|
|
|
|
// 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 logic
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
_ "embed"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"html/template"
|
|
|
|
|
"io"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/q191201771/naza/pkg/nazajson"
|
|
|
|
|
|
|
|
|
|
"github.com/q191201771/naza/pkg/nazahttp"
|
|
|
|
|
|
|
|
|
|
"github.com/q191201771/lal/pkg/base"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
//go:embed http_an__lal.html
|
|
|
|
|
var webUITpl string
|
|
|
|
|
|
|
|
|
|
type HttpApiServer struct {
|
|
|
|
|
addr string
|
|
|
|
|
sm *ServerManager
|
|
|
|
|
|
|
|
|
|
ln net.Listener
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewHttpApiServer(addr string, sm *ServerManager) *HttpApiServer {
|
|
|
|
|
return &HttpApiServer{
|
|
|
|
|
addr: addr,
|
|
|
|
|
sm: sm,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) Listen() (err error) {
|
|
|
|
|
if h.ln, err = net.Listen("tcp", h.addr); err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
Log.Infof("start http-api server listen. addr=%s", h.addr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) RunLoop() error {
|
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
|
|
|
|
|
mux.HandleFunc("/lal.html", h.webUIHandler)
|
|
|
|
|
|
|
|
|
|
mux.HandleFunc("/api/stat/group", h.statGroupHandler)
|
|
|
|
|
mux.HandleFunc("/api/stat/all_group", h.statAllGroupHandler)
|
|
|
|
|
mux.HandleFunc("/api/stat/lal_info", h.statLalInfoHandler)
|
|
|
|
|
|
|
|
|
|
mux.HandleFunc("/api/ctrl/start_relay_pull", h.ctrlStartRelayPullHandler)
|
|
|
|
|
mux.HandleFunc("/api/ctrl/stop_relay_pull", h.ctrlStopRelayPullHandler)
|
|
|
|
|
mux.HandleFunc("/api/ctrl/kick_session", h.ctrlKickSessionHandler)
|
|
|
|
|
mux.HandleFunc("/api/ctrl/start_rtp_pub", h.ctrlStartRtpPubHandler)
|
|
|
|
|
mux.HandleFunc("/api/ctrl/add_ip_blacklist", h.ctrlAddIpBlacklistHandler)
|
|
|
|
|
// 所有没有注册路由的走下面这个处理函数
|
|
|
|
|
mux.HandleFunc("/", h.notFoundHandler)
|
|
|
|
|
|
|
|
|
|
var srv http.Server
|
|
|
|
|
srv.Handler = mux
|
|
|
|
|
return srv.Serve(h.ln)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO chef: dispose
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) statLalInfoHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiStatLalInfoResp
|
|
|
|
|
v.ErrorCode = base.ErrorCodeSucc
|
|
|
|
|
v.Desp = base.DespSucc
|
|
|
|
|
v.Data = h.sm.StatLalInfo()
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) statAllGroupHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiStatAllGroupResp
|
|
|
|
|
v.ErrorCode = base.ErrorCodeSucc
|
|
|
|
|
v.Desp = base.DespSucc
|
|
|
|
|
v.Data.Groups = h.sm.StatAllGroup()
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) statGroupHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiStatGroupResp
|
|
|
|
|
|
|
|
|
|
q := req.URL.Query()
|
|
|
|
|
streamName := q.Get("stream_name")
|
|
|
|
|
if streamName == "" {
|
|
|
|
|
v.ErrorCode = base.ErrorCodeParamMissing
|
|
|
|
|
v.Desp = base.DespParamMissing
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v.Data = h.sm.StatGroup(streamName)
|
|
|
|
|
if v.Data == nil {
|
|
|
|
|
v.ErrorCode = base.ErrorCodeGroupNotFound
|
|
|
|
|
v.Desp = base.DespGroupNotFound
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v.ErrorCode = base.ErrorCodeSucc
|
|
|
|
|
v.Desp = base.DespSucc
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) ctrlStartRelayPullHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiCtrlStartRelayPullResp
|
|
|
|
|
var info base.ApiCtrlStartRelayPullReq
|
|
|
|
|
|
|
|
|
|
j, err := unmarshalRequestJsonBody(req, &info, "url")
|
|
|
|
|
if err != nil {
|
|
|
|
|
Log.Warnf("http api start pull error. err=%+v", err)
|
|
|
|
|
v.ErrorCode = base.ErrorCodeParamMissing
|
|
|
|
|
v.Desp = base.DespParamMissing
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !j.Exist("pull_timeout_ms") {
|
|
|
|
|
info.PullTimeoutMs = DefaultApiCtrlStartRelayPullReqPullTimeoutMs
|
|
|
|
|
}
|
|
|
|
|
if !j.Exist("pull_retry_num") {
|
|
|
|
|
info.PullRetryNum = base.PullRetryNumNever
|
|
|
|
|
}
|
|
|
|
|
if !j.Exist("auto_stop_pull_after_no_out_ms") {
|
|
|
|
|
info.AutoStopPullAfterNoOutMs = base.AutoStopPullAfterNoOutMsNever
|
|
|
|
|
}
|
|
|
|
|
if !j.Exist("rtsp_mode") {
|
|
|
|
|
info.RtspMode = base.RtspModeTcp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.Infof("http api start pull. req info=%+v", info)
|
|
|
|
|
|
|
|
|
|
resp := h.sm.CtrlStartRelayPull(info)
|
|
|
|
|
feedback(resp, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) ctrlStopRelayPullHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiCtrlStopRelayPullResp
|
|
|
|
|
|
|
|
|
|
q := req.URL.Query()
|
|
|
|
|
streamName := q.Get("stream_name")
|
|
|
|
|
if streamName == "" {
|
|
|
|
|
v.ErrorCode = base.ErrorCodeParamMissing
|
|
|
|
|
v.Desp = base.DespParamMissing
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.Infof("http api stop pull. stream_name=%s", streamName)
|
|
|
|
|
|
|
|
|
|
resp := h.sm.CtrlStopRelayPull(streamName)
|
|
|
|
|
feedback(resp, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) ctrlKickSessionHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiCtrlKickSessionResp
|
|
|
|
|
var info base.ApiCtrlKickSessionReq
|
|
|
|
|
|
|
|
|
|
_, err := unmarshalRequestJsonBody(req, &info, "stream_name", "session_id")
|
|
|
|
|
if err != nil {
|
|
|
|
|
Log.Warnf("http api kick session error. err=%+v", err)
|
|
|
|
|
v.ErrorCode = base.ErrorCodeParamMissing
|
|
|
|
|
v.Desp = base.DespParamMissing
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.Infof("http api kick session. req info=%+v", info)
|
|
|
|
|
|
|
|
|
|
resp := h.sm.CtrlKickSession(info)
|
|
|
|
|
feedback(resp, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) ctrlStartRtpPubHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiCtrlStartRtpPubResp
|
|
|
|
|
var info base.ApiCtrlStartRtpPubReq
|
|
|
|
|
|
|
|
|
|
j, err := unmarshalRequestJsonBody(req, &info, "stream_name")
|
|
|
|
|
if err != nil {
|
|
|
|
|
Log.Warnf("http api start rtp pub error. err=%+v", err)
|
|
|
|
|
v.ErrorCode = base.ErrorCodeParamMissing
|
|
|
|
|
v.Desp = base.DespParamMissing
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !j.Exist("timeout_ms") {
|
|
|
|
|
info.TimeoutMs = DefaultApiCtrlStartRtpPubReqTimeoutMs
|
|
|
|
|
}
|
|
|
|
|
// 不存在时默认0值的,不需要手动写了
|
|
|
|
|
//if !j.Exist("port") {
|
|
|
|
|
// info.Port = 0
|
|
|
|
|
//}
|
|
|
|
|
//if !j.Exist("is_tcp_flag") {
|
|
|
|
|
// info.IsTcpFlag = 0
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
Log.Infof("http api start rtp pub. req info=%+v", info)
|
|
|
|
|
|
|
|
|
|
resp := h.sm.CtrlStartRtpPub(info)
|
|
|
|
|
feedback(resp, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) ctrlAddIpBlacklistHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
var v base.ApiCtrlAddIpBlacklistResp
|
|
|
|
|
var info base.ApiCtrlAddIpBlacklistReq
|
|
|
|
|
|
|
|
|
|
_, err := unmarshalRequestJsonBody(req, &info, "ip", "duration_sec")
|
|
|
|
|
if err != nil {
|
|
|
|
|
Log.Warnf("http api add ip blacklist error. err=%+v", err)
|
|
|
|
|
v.ErrorCode = base.ErrorCodeParamMissing
|
|
|
|
|
v.Desp = base.DespParamMissing
|
|
|
|
|
feedback(v, w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.Infof("http api add ip blacklist. req info=%+v", info)
|
|
|
|
|
|
|
|
|
|
resp := h.sm.CtrlAddIpBlacklist(info)
|
|
|
|
|
feedback(resp, w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) webUIHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
t, err := template.New("webUI").Parse(webUITpl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
Log.Errorf("invaild html template: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lalInfo := h.sm.StatLalInfo()
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"ServerID": lalInfo.ServerId,
|
|
|
|
|
"LalVersion": lalInfo.LalVersion,
|
|
|
|
|
"ApiVersion": lalInfo.ApiVersion,
|
|
|
|
|
"NotifyVersion": lalInfo.NotifyVersion,
|
|
|
|
|
"WebUiVersion": lalInfo.WebUiVersion,
|
|
|
|
|
"StartTime": lalInfo.StartTime,
|
|
|
|
|
}
|
|
|
|
|
for _, item := range strings.Split(lalInfo.BinInfo, ". ") {
|
|
|
|
|
if index := strings.Index(item, "="); index != -1 {
|
|
|
|
|
k := item[:index]
|
|
|
|
|
v := strings.TrimPrefix(strings.TrimSuffix(item[index:], "."), "=")
|
|
|
|
|
data[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.Execute(w, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpApiServer) notFoundHandler(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
Log.Warnf("invalid http-api request. uri=%s, raddr=%s", req.RequestURI, req.RemoteAddr)
|
|
|
|
|
//w.WriteHeader(http.StatusNotFound)
|
|
|
|
|
feedback(base.ApiNotFoundRespFn(), w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func feedback(v interface{}, w http.ResponseWriter) {
|
|
|
|
|
resp, _ := json.Marshal(v)
|
|
|
|
|
w.Header().Add("Server", base.LalHttpApiServer)
|
|
|
|
|
base.AddCorsHeaders(w)
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
_, _ = w.Write(resp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unmarshalRequestJsonBody
|
|
|
|
|
//
|
|
|
|
|
// TODO(chef): [refactor] 搬到naza中 202205
|
|
|
|
|
func unmarshalRequestJsonBody(r *http.Request, info interface{}, keyFieldList ...string) (nazajson.Json, error) {
|
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nazajson.Json{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
j, err := nazajson.New(body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return j, err
|
|
|
|
|
}
|
|
|
|
|
for _, kf := range keyFieldList {
|
|
|
|
|
if !j.Exist(kf) {
|
|
|
|
|
return j, nazahttp.ErrParamMissing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return j, json.Unmarshal(body, info)
|
|
|
|
|
}
|