|
|
// Copyright 2019, 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 (
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
"io/ioutil"
|
|
|
"os"
|
|
|
"strings"
|
|
|
|
|
|
"github.com/q191201771/lal/pkg/base"
|
|
|
"github.com/q191201771/lal/pkg/hls"
|
|
|
"github.com/q191201771/naza/pkg/nazajson"
|
|
|
"github.com/q191201771/naza/pkg/nazalog"
|
|
|
)
|
|
|
|
|
|
const ConfVersion = "v0.2.2"
|
|
|
|
|
|
const (
|
|
|
defaultHLSCleanupMode = hls.CleanupModeInTheEnd
|
|
|
defaultHTTPFLVURLPattern = "/live/"
|
|
|
defaultHTTPTSURLPattern = "/live/"
|
|
|
defaultHLSURLPattern = "/hls/"
|
|
|
)
|
|
|
|
|
|
type Config struct {
|
|
|
ConfVersion string `json:"conf_version"`
|
|
|
RTMPConfig RTMPConfig `json:"rtmp"`
|
|
|
DefaultHTTPConfig DefaultHTTPConfig `json:"default_http"`
|
|
|
HTTPFLVConfig HTTPFLVConfig `json:"httpflv"`
|
|
|
HLSConfig HLSConfig `json:"hls"`
|
|
|
HTTPTSConfig HTTPTSConfig `json:"httpts"`
|
|
|
RTSPConfig RTSPConfig `json:"rtsp"`
|
|
|
RecordConfig RecordConfig `json:"record"`
|
|
|
RelayPushConfig RelayPushConfig `json:"relay_push"`
|
|
|
RelayPullConfig RelayPullConfig `json:"relay_pull"`
|
|
|
|
|
|
HTTPAPIConfig HTTPAPIConfig `json:"http_api"`
|
|
|
ServerID string `json:"server_id"`
|
|
|
HTTPNotifyConfig HTTPNotifyConfig `json:"http_notify"`
|
|
|
PProfConfig PProfConfig `json:"pprof"`
|
|
|
LogConfig nazalog.Option `json:"log"`
|
|
|
}
|
|
|
|
|
|
type RTMPConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
Addr string `json:"addr"`
|
|
|
GOPNum int `json:"gop_num"`
|
|
|
MergeWriteSize int `json:"merge_write_size"`
|
|
|
}
|
|
|
|
|
|
type DefaultHTTPConfig struct {
|
|
|
CommonHTTPAddrConfig
|
|
|
}
|
|
|
|
|
|
type HTTPFLVConfig struct {
|
|
|
CommonHTTPServerConfig
|
|
|
|
|
|
GOPNum int `json:"gop_num"`
|
|
|
}
|
|
|
|
|
|
type HTTPTSConfig struct {
|
|
|
CommonHTTPServerConfig
|
|
|
}
|
|
|
|
|
|
type HLSConfig struct {
|
|
|
CommonHTTPServerConfig
|
|
|
|
|
|
UseMemoryAsDiskFlag bool `json:"use_memory_as_disk_flag"`
|
|
|
hls.MuxerConfig
|
|
|
}
|
|
|
|
|
|
type RTSPConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
Addr string `json:"addr"`
|
|
|
}
|
|
|
|
|
|
type RecordConfig struct {
|
|
|
EnableFLV bool `json:"enable_flv"`
|
|
|
FLVOutPath string `json:"flv_out_path"`
|
|
|
EnableMPEGTS bool `json:"enable_mpegts"`
|
|
|
MPEGTSOutPath string `json:"mpegts_out_path"`
|
|
|
}
|
|
|
|
|
|
type RelayPushConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
AddrList []string `json:"addr_list"`
|
|
|
}
|
|
|
|
|
|
type RelayPullConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
Addr string `json:"addr"`
|
|
|
}
|
|
|
|
|
|
type HTTPAPIConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
Addr string `json:"addr"`
|
|
|
}
|
|
|
|
|
|
type HTTPNotifyConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
UpdateIntervalSec int `json:"update_interval_sec"`
|
|
|
OnServerStart string `json:"on_server_start"`
|
|
|
OnUpdate string `json:"on_update"`
|
|
|
OnPubStart string `json:"on_pub_start"`
|
|
|
OnPubStop string `json:"on_pub_stop"`
|
|
|
OnSubStart string `json:"on_sub_start"`
|
|
|
OnSubStop string `json:"on_sub_stop"`
|
|
|
OnRTMPConnect string `json:"on_rtmp_connect"`
|
|
|
}
|
|
|
|
|
|
type PProfConfig struct {
|
|
|
Enable bool `json:"enable"`
|
|
|
Addr string `json:"addr"`
|
|
|
}
|
|
|
|
|
|
type CommonHTTPServerConfig struct {
|
|
|
CommonHTTPAddrConfig
|
|
|
|
|
|
Enable bool `json:"enable"`
|
|
|
EnableHTTPS bool `json:"enable_https"`
|
|
|
URLPattern string `json:"url_pattern"`
|
|
|
}
|
|
|
|
|
|
type CommonHTTPAddrConfig struct {
|
|
|
HTTPListenAddr string `json:"http_listen_addr"`
|
|
|
HTTPSListenAddr string `json:"https_listen_addr"`
|
|
|
HTTPSCertFile string `json:"https_cert_file"`
|
|
|
HTTPSKeyFile string `json:"https_key_file"`
|
|
|
}
|
|
|
|
|
|
func LoadConfAndInitLog(confFile string) *Config {
|
|
|
// 读取配置文件并解析原始内容
|
|
|
rawContent, err := ioutil.ReadFile(confFile)
|
|
|
if err != nil {
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "read conf file failed. file=%s err=%+v", confFile, err)
|
|
|
base.OSExitAndWaitPressIfWindows(1)
|
|
|
}
|
|
|
if err = json.Unmarshal(rawContent, &config); err != nil {
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "unmarshal conf file failed. file=%s err=%+v", confFile, err)
|
|
|
base.OSExitAndWaitPressIfWindows(1)
|
|
|
}
|
|
|
j, err := nazajson.New(rawContent)
|
|
|
if err != nil {
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "nazajson unmarshal conf file failed. file=%s err=%+v", confFile, err)
|
|
|
base.OSExitAndWaitPressIfWindows(1)
|
|
|
}
|
|
|
|
|
|
// 初始化日志,注意,这一步尽量提前,使得后续的日志内容按我们的日志配置输出
|
|
|
// 日志配置项不存在时,设置默认值
|
|
|
if !j.Exist("log.level") {
|
|
|
config.LogConfig.Level = nazalog.LevelDebug
|
|
|
}
|
|
|
if !j.Exist("log.filename") {
|
|
|
config.LogConfig.Filename = "./logs/lalserver.log"
|
|
|
}
|
|
|
if !j.Exist("log.is_to_stdout") {
|
|
|
config.LogConfig.IsToStdout = true
|
|
|
}
|
|
|
if !j.Exist("log.is_rotate_daily") {
|
|
|
config.LogConfig.IsRotateDaily = true
|
|
|
}
|
|
|
if !j.Exist("log.short_file_flag") {
|
|
|
config.LogConfig.ShortFileFlag = true
|
|
|
}
|
|
|
if !j.Exist("log.timestamp_flag") {
|
|
|
config.LogConfig.TimestampFlag = true
|
|
|
}
|
|
|
if !j.Exist("log.timestamp_with_ms_flag") {
|
|
|
config.LogConfig.TimestampWithMSFlag = true
|
|
|
}
|
|
|
if !j.Exist("log.level_flag") {
|
|
|
config.LogConfig.LevelFlag = true
|
|
|
}
|
|
|
if !j.Exist("log.assert_behavior") {
|
|
|
config.LogConfig.AssertBehavior = nazalog.AssertError
|
|
|
}
|
|
|
if err := nazalog.Init(func(option *nazalog.Option) {
|
|
|
*option = config.LogConfig
|
|
|
}); err != nil {
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "initial log failed. err=%+v\n", err)
|
|
|
base.OSExitAndWaitPressIfWindows(1)
|
|
|
}
|
|
|
nazalog.Info("initial log succ.")
|
|
|
|
|
|
// 打印Logo
|
|
|
nazalog.Info(`
|
|
|
__ ___ __
|
|
|
/ / / | / /
|
|
|
/ / / /| | / /
|
|
|
/ /___/ ___ |/ /___
|
|
|
/_____/_/ |_/_____/
|
|
|
`)
|
|
|
|
|
|
// 检查配置版本号是否匹配
|
|
|
if config.ConfVersion != ConfVersion {
|
|
|
nazalog.Warnf("config version invalid. conf version of lalserver=%s, conf version of config file=%s",
|
|
|
ConfVersion, config.ConfVersion)
|
|
|
}
|
|
|
|
|
|
// 检查一级配置项
|
|
|
keyFieldList := []string{
|
|
|
"rtmp",
|
|
|
"httpflv",
|
|
|
"hls",
|
|
|
"httpts",
|
|
|
"rtsp",
|
|
|
"record",
|
|
|
"relay_push",
|
|
|
"relay_pull",
|
|
|
"http_api",
|
|
|
"http_notify",
|
|
|
"pprof",
|
|
|
"log",
|
|
|
}
|
|
|
for _, kf := range keyFieldList {
|
|
|
if !j.Exist(kf) {
|
|
|
nazalog.Warnf("missing config item %s", kf)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果具体的HTTP应用没有设置HTTP监听相关的配置,则尝试使用全局配置
|
|
|
mergeCommonHTTPAddrConfig(&config.HTTPFLVConfig.CommonHTTPAddrConfig, &config.DefaultHTTPConfig.CommonHTTPAddrConfig)
|
|
|
mergeCommonHTTPAddrConfig(&config.HTTPTSConfig.CommonHTTPAddrConfig, &config.DefaultHTTPConfig.CommonHTTPAddrConfig)
|
|
|
mergeCommonHTTPAddrConfig(&config.HLSConfig.CommonHTTPAddrConfig, &config.DefaultHTTPConfig.CommonHTTPAddrConfig)
|
|
|
|
|
|
// 配置不存在时,设置默认值
|
|
|
if (config.HLSConfig.Enable || config.HLSConfig.EnableHTTPS) && !j.Exist("hls.cleanup_mode") {
|
|
|
nazalog.Warnf("config hls.cleanup_mode not exist. set to default which is %d", defaultHLSCleanupMode)
|
|
|
config.HLSConfig.CleanupMode = defaultHLSCleanupMode
|
|
|
}
|
|
|
if (config.HTTPFLVConfig.Enable || config.HTTPFLVConfig.EnableHTTPS) && !j.Exist("httpflv.url_pattern") {
|
|
|
nazalog.Warnf("config httpflv.url_pattern not exist. set to default wchich is %s", defaultHTTPFLVURLPattern)
|
|
|
config.HTTPFLVConfig.URLPattern = defaultHTTPFLVURLPattern
|
|
|
}
|
|
|
if (config.HTTPTSConfig.Enable || config.HTTPTSConfig.EnableHTTPS) && !j.Exist("httpts.url_pattern") {
|
|
|
nazalog.Warnf("config httpts.url_pattern not exist. set to default wchich is %s", defaultHTTPTSURLPattern)
|
|
|
config.HTTPTSConfig.URLPattern = defaultHTTPTSURLPattern
|
|
|
}
|
|
|
if (config.HLSConfig.Enable || config.HLSConfig.EnableHTTPS) && !j.Exist("hls.url_pattern") {
|
|
|
nazalog.Warnf("config hls.url_pattern not exist. set to default wchich is %s", defaultHLSURLPattern)
|
|
|
config.HTTPFLVConfig.URLPattern = defaultHLSURLPattern
|
|
|
}
|
|
|
|
|
|
// 对一些常见的格式错误做修复
|
|
|
// 确保url pattern以`/`开始,并以`/`结束
|
|
|
if urlPattern, changed := ensureStartAndEndWithSlash(config.HTTPFLVConfig.URLPattern); changed {
|
|
|
nazalog.Warnf("fix config. httpflv.url_pattern %s -> %s", config.HTTPFLVConfig.URLPattern, urlPattern)
|
|
|
config.HTTPFLVConfig.URLPattern = urlPattern
|
|
|
}
|
|
|
if urlPattern, changed := ensureStartAndEndWithSlash(config.HTTPTSConfig.URLPattern); changed {
|
|
|
nazalog.Warnf("fix config. httpts.url_pattern %s -> %s", config.HTTPTSConfig.URLPattern, urlPattern)
|
|
|
config.HTTPFLVConfig.URLPattern = urlPattern
|
|
|
}
|
|
|
if urlPattern, changed := ensureStartAndEndWithSlash(config.HLSConfig.URLPattern); changed {
|
|
|
nazalog.Warnf("fix config. hls.url_pattern %s -> %s", config.HLSConfig.URLPattern, urlPattern)
|
|
|
config.HTTPFLVConfig.URLPattern = urlPattern
|
|
|
}
|
|
|
|
|
|
// 把配置文件原始内容中的换行去掉,使得打印日志时紧凑一些
|
|
|
lines := strings.Split(string(rawContent), "\n")
|
|
|
if len(lines) == 1 {
|
|
|
lines = strings.Split(string(rawContent), "\r\n")
|
|
|
}
|
|
|
var tlines []string
|
|
|
for _, l := range lines {
|
|
|
tlines = append(tlines, strings.TrimSpace(l))
|
|
|
}
|
|
|
compactRawContent := strings.Join(tlines, " ")
|
|
|
nazalog.Infof("load conf file succ. filename=%s, raw content=%s parsed=%+v", confFile, compactRawContent, config)
|
|
|
|
|
|
return config
|
|
|
}
|
|
|
func mergeCommonHTTPAddrConfig(dst, src *CommonHTTPAddrConfig) {
|
|
|
if dst.HTTPListenAddr == "" && src.HTTPListenAddr != "" {
|
|
|
dst.HTTPListenAddr = src.HTTPListenAddr
|
|
|
}
|
|
|
if dst.HTTPSListenAddr == "" && src.HTTPSListenAddr != "" {
|
|
|
dst.HTTPSListenAddr = src.HTTPSListenAddr
|
|
|
}
|
|
|
if dst.HTTPSCertFile == "" && src.HTTPSCertFile != "" {
|
|
|
dst.HTTPSCertFile = src.HTTPSCertFile
|
|
|
}
|
|
|
if dst.HTTPSKeyFile == "" && src.HTTPSKeyFile != "" {
|
|
|
dst.HTTPSKeyFile = src.HTTPSKeyFile
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func ensureStartWithSlash(in string) (out string, changed bool) {
|
|
|
if in == "" {
|
|
|
return in, false
|
|
|
}
|
|
|
if in[0] == '/' {
|
|
|
return in, false
|
|
|
}
|
|
|
return "/" + in, true
|
|
|
}
|
|
|
|
|
|
func ensureEndWithSlash(in string) (out string, changed bool) {
|
|
|
if in == "" {
|
|
|
return in, false
|
|
|
}
|
|
|
if in[len(in)-1] == '/' {
|
|
|
return in, false
|
|
|
}
|
|
|
return in + "/", true
|
|
|
}
|
|
|
|
|
|
func ensureStartAndEndWithSlash(in string) (out string, changed bool) {
|
|
|
n, c := ensureStartWithSlash(in)
|
|
|
n2, c2 := ensureEndWithSlash(n)
|
|
|
return n2, c || c2
|
|
|
}
|