You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/pkg/logic/config.go

338 lines
12 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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.4"
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"` // TODO(chef): refactor 更名为gop_cache_num
MergeWriteSize int `json:"merge_write_size"`
AddDummyAudioEnable bool `json:"add_dummy_audio_enable"`
AddDummyAudioWaitAudioMs int `json:"add_dummy_audio_wait_audio_ms"`
}
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)
}
// 初始化日志模块,注意,这一步尽量提前,使得后续的日志内容按我们的日志配置输出
//
// 日志配置项不存在时,设置默认值
//
// 注意,由于此时日志模块还没有初始化,所以有日志需要打印时,我们采用先缓存后打印(日志模块初始化成功后再打印)的方式
var cacheLog []string
if !j.Exist("log.level") {
config.LogConfig.Level = nazalog.LevelDebug
cacheLog = append(cacheLog, fmt.Sprintf("log.level=%s", config.LogConfig.Level.ReadableString()))
}
if !j.Exist("log.filename") {
config.LogConfig.Filename = "./logs/lalserver.log"
cacheLog = append(cacheLog, fmt.Sprintf("log.filename=%s", config.LogConfig.Filename))
}
if !j.Exist("log.is_to_stdout") {
config.LogConfig.IsToStdout = true
cacheLog = append(cacheLog, fmt.Sprintf("log.is_to_stdout=%v", config.LogConfig.IsToStdout))
}
if !j.Exist("log.is_rotate_daily") {
config.LogConfig.IsRotateDaily = true
cacheLog = append(cacheLog, fmt.Sprintf("log.is_rotate_daily=%v", config.LogConfig.IsRotateDaily))
}
if !j.Exist("log.short_file_flag") {
config.LogConfig.ShortFileFlag = true
cacheLog = append(cacheLog, fmt.Sprintf("log.short_file_flag=%v", config.LogConfig.ShortFileFlag))
}
if !j.Exist("log.timestamp_flag") {
config.LogConfig.TimestampFlag = true
cacheLog = append(cacheLog, fmt.Sprintf("log.timestamp_flag=%v", config.LogConfig.TimestampFlag))
}
if !j.Exist("log.timestamp_with_ms_flag") {
config.LogConfig.TimestampWithMsFlag = true
cacheLog = append(cacheLog, fmt.Sprintf("log.timestamp_with_ms_flag=%v", config.LogConfig.TimestampWithMsFlag))
}
if !j.Exist("log.level_flag") {
config.LogConfig.LevelFlag = true
cacheLog = append(cacheLog, fmt.Sprintf("log.level_flag=%v", config.LogConfig.LevelFlag))
}
if !j.Exist("log.assert_behavior") {
config.LogConfig.AssertBehavior = nazalog.AssertError
cacheLog = append(cacheLog, fmt.Sprintf("log.assert_behavior=%s", config.LogConfig.AssertBehavior.ReadableString()))
}
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)
}
// 做个全量字段检查缺失的字段Go中会先设置为零值
notExistFields, err := nazajson.CollectNotExistFields(rawContent, config,
"log.",
"default_http.http_listen_addr", "default_http.https_listen_addr", "default_http.https_cert_file", "default_http.https_key_file",
"httpflv.http_listen_addr", "httpflv.https_listen_addr", "httpflv.https_cert_file", "httpflv.https_key_file",
"hls.http_listen_addr", "hls.https_listen_addr", "hls.https_cert_file", "hls.https_key_file",
"httpts.http_listen_addr", "httpts.https_listen_addr", "httpts.https_cert_file", "httpts.https_key_file",
)
if err != nil {
nazalog.Warnf("config nazajson collect not exist fields failed. err=%+v", err)
}
if len(notExistFields) != 0 {
nazalog.Warnf("config some fields do not exist which have been set to the zero value. fields=%+v", notExistFields)
}
// 日志字段检查,缺失的字段,打印前面设置的默认值
if len(cacheLog) > 0 {
nazalog.Warnf("config some log fields do not exist which have been set to default value. %s", strings.Join(cacheLog, ", "))
}
// 如果具体的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
}