|
|
// Copyright 2020, Chef. All rights reserved.
|
|
|
// https://github.com/q191201771/lal
|
|
|
//
|
|
|
// Use of this source code is governed by a MIT-style license
|
|
|
// that can be found in the License file.
|
|
|
//
|
|
|
// Author: Chef (191201771@qq.com)
|
|
|
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"io/ioutil"
|
|
|
"net"
|
|
|
"net/http"
|
|
|
"strings"
|
|
|
|
|
|
"github.com/q191201771/lal/app/demo/dispatch/datamanager"
|
|
|
"github.com/q191201771/lal/pkg/base"
|
|
|
"github.com/q191201771/naza/pkg/nazahttp"
|
|
|
"github.com/q191201771/naza/pkg/nazalog"
|
|
|
"github.com/q191201771/naza/pkg/unique"
|
|
|
)
|
|
|
|
|
|
// 结合lalserver的HTTP Notify事件通知,以及HTTP API接口,
|
|
|
// 简单演示如何试验一个简单的调度服务,
|
|
|
// 使得多个lalserver节点可以组成一个集群,
|
|
|
// 集群内的所有节点功能都是相同的,
|
|
|
// 你可以将流推送至任意一个节点,并从任意一个节点拉流,
|
|
|
// 同一路流,推流和拉流可以在不同的节点。
|
|
|
//
|
|
|
// 本demo的数据存储在内存中,所以存在单点风险,
|
|
|
// 生产环境可以把数据存储在redis、mysql等数据库中,
|
|
|
// 多个调度节点从数据库中读写数据。
|
|
|
|
|
|
type Server struct {
|
|
|
rtmpAddr string
|
|
|
apiAddr string
|
|
|
}
|
|
|
|
|
|
// config
|
|
|
var (
|
|
|
listenAddr = ":10101"
|
|
|
serverID2Server = map[string]Server{
|
|
|
"1": {
|
|
|
rtmpAddr: "127.0.0.1:19350",
|
|
|
apiAddr: "127.0.0.1:8083",
|
|
|
},
|
|
|
"2": {
|
|
|
rtmpAddr: "127.0.0.1:19550",
|
|
|
apiAddr: "127.0.0.1:8283",
|
|
|
},
|
|
|
}
|
|
|
pullSecretParam = "lal_cluster_inner_pull=1"
|
|
|
)
|
|
|
|
|
|
var (
|
|
|
dataManager datamanager.DataManger
|
|
|
)
|
|
|
|
|
|
func OnPubStartHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
id := unique.GenUniqueKey("ReqID")
|
|
|
|
|
|
var info base.PubStartInfo
|
|
|
if err := nazahttp.UnmarshalRequestJsonBody(r, &info); err != nil {
|
|
|
nazalog.Error(err)
|
|
|
return
|
|
|
}
|
|
|
nazalog.Infof("[%s] on_pub_start. info=%+v", id, info)
|
|
|
|
|
|
// 演示如何踢掉session,服务于鉴权失败等场景
|
|
|
//if info.URLParam == "" {
|
|
|
if info.SessionID == "RTMPPUBSUB1" {
|
|
|
reqServer, exist := serverID2Server[info.ServerID]
|
|
|
if !exist {
|
|
|
nazalog.Errorf("[%s] req server id invalid.", id)
|
|
|
return
|
|
|
}
|
|
|
url := fmt.Sprintf("http://%s/api/ctrl/kick_out_session", reqServer.apiAddr)
|
|
|
var b base.APICtrlKickOutSession
|
|
|
b.StreamName = info.StreamName
|
|
|
b.SessionID = info.SessionID
|
|
|
|
|
|
nazalog.Infof("[%s] ctrl kick out session. send to %s with %+v", id, reqServer.apiAddr, b)
|
|
|
if _, err := nazahttp.PostJson(url, b, nil); err != nil {
|
|
|
nazalog.Errorf("[%s] post json error. err=%+v", id, err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
dataManager.AddPub(info.StreamName, info.SessionID)
|
|
|
}
|
|
|
|
|
|
func OnPubStopHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
id := unique.GenUniqueKey("ReqID")
|
|
|
|
|
|
var info base.PubStopInfo
|
|
|
if err := nazahttp.UnmarshalRequestJsonBody(r, &info); err != nil {
|
|
|
nazalog.Error(err)
|
|
|
return
|
|
|
}
|
|
|
nazalog.Infof("[%s] on_pub_stop. info=%+v", id, info)
|
|
|
|
|
|
dataManager.DelPub(info.StreamName, info.ServerID)
|
|
|
}
|
|
|
|
|
|
func OnSubStartHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
id := unique.GenUniqueKey("ReqID")
|
|
|
|
|
|
var info base.SubStartInfo
|
|
|
if err := nazahttp.UnmarshalRequestJsonBody(r, &info); err != nil {
|
|
|
nazalog.Error(err)
|
|
|
return
|
|
|
}
|
|
|
nazalog.Infof("[%s] on_sub_start. info=%+v", id, info)
|
|
|
|
|
|
// sub拉流时,判断是否需要触发pull级联拉流
|
|
|
// 1. 是内部级联拉流,不需要触发
|
|
|
if strings.Contains(info.URLParam, pullSecretParam) {
|
|
|
nazalog.Infof("[%s] sub is pull by other node, ignore.", id)
|
|
|
return
|
|
|
}
|
|
|
// 2. 已经存在输入流,不需要触发
|
|
|
if info.HasInSession {
|
|
|
nazalog.Infof("[%s] in not empty, ignore.", id)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 3. 非法节点,本服务没有配置这个节点
|
|
|
reqServer, exist := serverID2Server[info.ServerID]
|
|
|
if !exist {
|
|
|
nazalog.Errorf("[%s] req server id invalid.", id)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
pubServerID, exist := dataManager.QueryPub(info.StreamName)
|
|
|
// 4. 没有查到流所在节点,不需要触发
|
|
|
if !exist {
|
|
|
nazalog.Infof("[%s] pub not exist, ignore.", id)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// TODO chef: 5. 这里的容错是否会出现?是否可以去掉?
|
|
|
pubServer, exist := serverID2Server[pubServerID]
|
|
|
if !exist {
|
|
|
nazalog.Errorf("[%s] pub server id invalid. serverID=%s", id, pubServerID)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 向pub所在节点,发送pull级联拉流的命令
|
|
|
url := fmt.Sprintf("http://%s/api/ctrl/start_pull", reqServer.apiAddr)
|
|
|
var b base.APICtrlStartPullReq
|
|
|
b.Protocol = base.ProtocolRTMP
|
|
|
b.Addr = pubServer.rtmpAddr
|
|
|
b.AppName = info.AppName
|
|
|
b.StreamName = info.StreamName
|
|
|
b.URLParam = pullSecretParam
|
|
|
|
|
|
nazalog.Infof("[%s] ctrl pull. send to %s with %+v", id, reqServer.apiAddr, b)
|
|
|
if _, err := nazahttp.PostJson(url, b, nil); err != nil {
|
|
|
nazalog.Errorf("[%s] post json error. err=%+v", id, err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func OnSubStopHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
id := unique.GenUniqueKey("ReqID")
|
|
|
|
|
|
var info base.SubStopInfo
|
|
|
if err := nazahttp.UnmarshalRequestJsonBody(r, &info); err != nil {
|
|
|
nazalog.Error(err)
|
|
|
return
|
|
|
}
|
|
|
nazalog.Infof("[%s] on_sub_stop. info=%+v", id, info)
|
|
|
|
|
|
// 没什么好做的
|
|
|
// 目前lalserver在sub为空时,内部会主动关闭pull
|
|
|
}
|
|
|
|
|
|
func OnUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
id := unique.GenUniqueKey("ReqID")
|
|
|
|
|
|
var info base.UpdateInfo
|
|
|
if err := nazahttp.UnmarshalRequestJsonBody(r, &info); err != nil {
|
|
|
nazalog.Error(err)
|
|
|
return
|
|
|
}
|
|
|
nazalog.Infof("[%s] on_update. info=%+v", id, info)
|
|
|
|
|
|
// TODO chef:
|
|
|
// 1. 更新pubStream2ServerID,去掉过期的,增加不存在的
|
|
|
// 2. 没有pub但是有sub的,触发ctrl pull
|
|
|
}
|
|
|
|
|
|
func logHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
b, _ := ioutil.ReadAll(r.Body)
|
|
|
nazalog.Infof("r=%+v, body=%s", r, b)
|
|
|
}
|
|
|
|
|
|
func main() {
|
|
|
dataManager = datamanager.NewDataManager(datamanager.DMTMemory)
|
|
|
|
|
|
l, err := net.Listen("tcp", listenAddr)
|
|
|
nazalog.Assert(nil, err)
|
|
|
|
|
|
m := http.NewServeMux()
|
|
|
m.HandleFunc("/on_pub_start", OnPubStartHandler)
|
|
|
m.HandleFunc("/on_pub_stop", OnPubStopHandler)
|
|
|
m.HandleFunc("/on_sub_start", OnSubStartHandler)
|
|
|
m.HandleFunc("/on_sub_stop", OnSubStopHandler)
|
|
|
m.HandleFunc("/on_update", OnUpdateHandler)
|
|
|
m.HandleFunc("/on_rtmp_connect", logHandler)
|
|
|
|
|
|
srv := http.Server{
|
|
|
Handler: m,
|
|
|
}
|
|
|
err = srv.Serve(l)
|
|
|
nazalog.Assert(nil, err)
|
|
|
}
|