mirror of https://github.com/q191201771/lal.git
commit
e5df0b4802
@ -0,0 +1,149 @@
|
||||
// Copyright 2021, 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 base
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/q191201771/naza/pkg/nazaerrors"
|
||||
)
|
||||
|
||||
// TODO(chef)
|
||||
// - 考虑移入naza中
|
||||
// - 考虑增加一个pattern全部未命中的mux回调
|
||||
|
||||
var (
|
||||
ErrAddrEmpty = errors.New("lal.base: http server addr empty")
|
||||
ErrMultiRegistForPattern = errors.New("lal.base: http server multiple registrations for pattern")
|
||||
)
|
||||
|
||||
const (
|
||||
NetworkTCP = "tcp"
|
||||
)
|
||||
|
||||
type LocalAddrCtx struct {
|
||||
IsHTTPS bool
|
||||
Addr string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
|
||||
Network string // 默认为NetworkTCP
|
||||
}
|
||||
|
||||
type HTTPServerManager struct {
|
||||
addr2ServerCtx map[string]*ServerCtx
|
||||
}
|
||||
|
||||
type ServerCtx struct {
|
||||
addrCtx LocalAddrCtx
|
||||
listener net.Listener
|
||||
httpServer http.Server
|
||||
mux *http.ServeMux
|
||||
pattern2Handler map[string]Handler
|
||||
}
|
||||
|
||||
func NewHTTPServerManager() *HTTPServerManager {
|
||||
return &HTTPServerManager{
|
||||
addr2ServerCtx: make(map[string]*ServerCtx),
|
||||
}
|
||||
}
|
||||
|
||||
type Handler func(http.ResponseWriter, *http.Request)
|
||||
|
||||
// @param pattern 必须以`/`开始,并以`/`结束
|
||||
func (s *HTTPServerManager) AddListen(addrCtx LocalAddrCtx, pattern string, handler Handler) error {
|
||||
var (
|
||||
ctx *ServerCtx
|
||||
mux *http.ServeMux
|
||||
ok bool
|
||||
)
|
||||
|
||||
if addrCtx.Addr == "" {
|
||||
return ErrAddrEmpty
|
||||
}
|
||||
|
||||
ctx, ok = s.addr2ServerCtx[addrCtx.Addr]
|
||||
if !ok {
|
||||
l, err := listen(addrCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mux = http.NewServeMux()
|
||||
ctx = &ServerCtx{
|
||||
addrCtx: addrCtx,
|
||||
listener: l,
|
||||
httpServer: http.Server{
|
||||
Handler: mux,
|
||||
},
|
||||
mux: mux,
|
||||
pattern2Handler: make(map[string]Handler),
|
||||
}
|
||||
s.addr2ServerCtx[addrCtx.Addr] = ctx
|
||||
}
|
||||
|
||||
// 路径相同,比较回调函数是否相同
|
||||
// 如果回调函数也相同,意味着重复绑定,这种情况是允许的
|
||||
// 如果回调函数不同,返回错误
|
||||
if prevHandler, ok := ctx.pattern2Handler[pattern]; ok {
|
||||
if reflect.ValueOf(prevHandler).Pointer() == reflect.ValueOf(handler).Pointer() {
|
||||
return nil
|
||||
} else {
|
||||
return ErrMultiRegistForPattern
|
||||
}
|
||||
}
|
||||
ctx.pattern2Handler[pattern] = handler
|
||||
|
||||
ctx.mux.HandleFunc(pattern, handler)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HTTPServerManager) RunLoop() error {
|
||||
errChan := make(chan error, len(s.addr2ServerCtx))
|
||||
|
||||
for _, v := range s.addr2ServerCtx {
|
||||
go func(ctx *ServerCtx) {
|
||||
errChan <- ctx.httpServer.Serve(ctx.listener)
|
||||
|
||||
_ = ctx.httpServer.Close()
|
||||
}(v)
|
||||
}
|
||||
|
||||
// 阻塞直到接到第一个error
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (s *HTTPServerManager) Dispose() error {
|
||||
var es []error
|
||||
for _, v := range s.addr2ServerCtx {
|
||||
err := v.httpServer.Close()
|
||||
es = append(es, err)
|
||||
}
|
||||
return nazaerrors.CombineErrors(es...)
|
||||
}
|
||||
|
||||
func listen(ctx LocalAddrCtx) (net.Listener, error) {
|
||||
if ctx.Network == "" {
|
||||
ctx.Network = NetworkTCP
|
||||
}
|
||||
|
||||
if !ctx.IsHTTPS {
|
||||
return net.Listen(ctx.Network, ctx.Addr)
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(ctx.CertFile, ctx.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
return tls.Listen(ctx.Network, ctx.Addr, tlsConfig)
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// Copyright 2021, 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 base
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPServerManager(t *testing.T) {
|
||||
//var err error
|
||||
//
|
||||
//var fnFLV = func(writer http.ResponseWriter, request *http.Request) {
|
||||
// nazalog.Debugf("> fnFLV. %+v, %+v", writer, request)
|
||||
// conn, bio, err := writer.(http.Hijacker).Hijack()
|
||||
// if err != nil {
|
||||
// nazalog.Errorf("hijack failed. err=%+v", err)
|
||||
// return
|
||||
// }
|
||||
// if bio.Reader.Buffered() != 0 || bio.Writer.Buffered() != 0 {
|
||||
// nazalog.Errorf("hijack but buffer not empty. rb=%d, wb=%d", bio.Reader.Buffered(), bio.Writer.Buffered())
|
||||
// }
|
||||
// nazalog.Debugf("%+v, %+v, %+v", conn, bio, err)
|
||||
//}
|
||||
//
|
||||
//sm := NewHTTPServerManager()
|
||||
//
|
||||
//err = sm.AddListen(
|
||||
// LocalAddrCtx{IsHTTPS: false, Addr: ":8080"},
|
||||
// "/live/",
|
||||
// fnFLV,
|
||||
//)
|
||||
//assert.Equal(t, nil, err)
|
||||
//
|
||||
//err = sm.AddListen(
|
||||
// LocalAddrCtx{IsHTTPS: true, Addr: ":4433", CertFile: "../../conf/cert.pem", KeyFile: "../../conf/key.pem"},
|
||||
// "/live/",
|
||||
// fnFLV,
|
||||
//)
|
||||
//assert.Equal(t, nil, err)
|
||||
//
|
||||
//err = sm.RunLoop()
|
||||
//assert.Equal(t, nil, err)
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
// 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 base
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/q191201771/naza/pkg/connection"
|
||||
)
|
||||
|
||||
type HTTPSubSession struct {
|
||||
HTTPSubSessionOption
|
||||
|
||||
suffix string
|
||||
conn connection.Connection
|
||||
prevConnStat connection.Stat
|
||||
staleStat *connection.Stat
|
||||
stat StatSession
|
||||
}
|
||||
|
||||
type HTTPSubSessionOption struct {
|
||||
Conn net.Conn
|
||||
ConnModOption connection.ModOption
|
||||
UK string // unique key
|
||||
Protocol string
|
||||
URLCtx URLContext
|
||||
IsWebSocket bool
|
||||
WebSocketKey string
|
||||
}
|
||||
|
||||
func NewHTTPSubSession(option HTTPSubSessionOption) *HTTPSubSession {
|
||||
s := &HTTPSubSession{
|
||||
HTTPSubSessionOption: option,
|
||||
conn: connection.New(option.Conn, option.ConnModOption),
|
||||
stat: StatSession{
|
||||
Protocol: option.Protocol,
|
||||
SessionID: option.UK,
|
||||
StartTime: time.Now().Format("2006-01-02 15:04:05.999"),
|
||||
RemoteAddr: option.Conn.RemoteAddr().String(),
|
||||
},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) RunLoop() error {
|
||||
buf := make([]byte, 128)
|
||||
_, err := session.conn.Read(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) WriteHTTPResponseHeader(b []byte) {
|
||||
if session.IsWebSocket {
|
||||
session.Write(UpdateWebSocketHeader(session.WebSocketKey))
|
||||
} else {
|
||||
session.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) Write(b []byte) {
|
||||
if session.IsWebSocket {
|
||||
wsHeader := WSHeader{
|
||||
Fin: true,
|
||||
Rsv1: false,
|
||||
Rsv2: false,
|
||||
Rsv3: false,
|
||||
Opcode: WSO_Binary,
|
||||
PayloadLength: uint64(len(b)),
|
||||
Masked: false,
|
||||
}
|
||||
session.write(MakeWSFrameHeader(wsHeader))
|
||||
}
|
||||
session.write(b)
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) Dispose() error {
|
||||
return session.conn.Close()
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) URL() string {
|
||||
return session.URLCtx.URL
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) AppName() string {
|
||||
return session.URLCtx.PathWithoutLastItem
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) StreamName() string {
|
||||
var suffix string
|
||||
switch session.Protocol {
|
||||
case ProtocolHTTPFLV:
|
||||
suffix = ".flv"
|
||||
case ProtocolHTTPTS:
|
||||
suffix = ".ts"
|
||||
default:
|
||||
Logger.Warnf("[%s] acquire stream name but protocol unknown.", session.UK)
|
||||
}
|
||||
return strings.TrimSuffix(session.URLCtx.LastItemOfPath, suffix)
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) RawQuery() string {
|
||||
return session.URLCtx.RawQuery
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) UniqueKey() string {
|
||||
return session.UK
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) GetStat() StatSession {
|
||||
currStat := session.conn.GetStat()
|
||||
session.stat.ReadBytesSum = currStat.ReadBytesSum
|
||||
session.stat.WroteBytesSum = currStat.WroteBytesSum
|
||||
return session.stat
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) UpdateStat(intervalSec uint32) {
|
||||
currStat := session.conn.GetStat()
|
||||
rDiff := currStat.ReadBytesSum - session.prevConnStat.ReadBytesSum
|
||||
session.stat.ReadBitrate = int(rDiff * 8 / 1024 / uint64(intervalSec))
|
||||
wDiff := currStat.WroteBytesSum - session.prevConnStat.WroteBytesSum
|
||||
session.stat.WriteBitrate = int(wDiff * 8 / 1024 / uint64(intervalSec))
|
||||
session.stat.Bitrate = session.stat.WriteBitrate
|
||||
session.prevConnStat = currStat
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) IsAlive() (readAlive, writeAlive bool) {
|
||||
currStat := session.conn.GetStat()
|
||||
if session.staleStat == nil {
|
||||
session.staleStat = new(connection.Stat)
|
||||
*session.staleStat = currStat
|
||||
return true, true
|
||||
}
|
||||
|
||||
readAlive = !(currStat.ReadBytesSum-session.staleStat.ReadBytesSum == 0)
|
||||
writeAlive = !(currStat.WroteBytesSum-session.staleStat.WroteBytesSum == 0)
|
||||
*session.staleStat = currStat
|
||||
return
|
||||
}
|
||||
|
||||
func (session *HTTPSubSession) write(b []byte) {
|
||||
_, _ = session.conn.Write(b)
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// Copyright 2021, 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 base
|
||||
|
||||
import "github.com/q191201771/naza/pkg/nazalog"
|
||||
|
||||
var (
|
||||
Logger = nazalog.GetGlobalLogger()
|
||||
)
|
@ -0,0 +1,58 @@
|
||||
// Copyright 2021, 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 hls
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/q191201771/lal/pkg/base"
|
||||
"github.com/q191201771/lal/pkg/mpegts"
|
||||
"github.com/q191201771/naza/pkg/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
fh []byte
|
||||
poped []base.RTMPMsg
|
||||
)
|
||||
|
||||
type qo struct {
|
||||
}
|
||||
|
||||
func (q *qo) OnPATPMT(b []byte) {
|
||||
fh = b
|
||||
}
|
||||
|
||||
func (q *qo) OnPop(msg base.RTMPMsg) {
|
||||
poped = append(poped, msg)
|
||||
}
|
||||
|
||||
func TestQueue(t *testing.T) {
|
||||
goldenRTMPMsg := []base.RTMPMsg{
|
||||
{
|
||||
Header: base.RTMPHeader{
|
||||
MsgTypeID: base.RTMPTypeIDAudio,
|
||||
},
|
||||
Payload: []byte{0xAF},
|
||||
},
|
||||
{
|
||||
Header: base.RTMPHeader{
|
||||
MsgTypeID: base.RTMPTypeIDVideo,
|
||||
},
|
||||
Payload: []byte{0x17},
|
||||
},
|
||||
}
|
||||
|
||||
q := &qo{}
|
||||
queue := NewQueue(8, q)
|
||||
for i := range goldenRTMPMsg {
|
||||
queue.Push(goldenRTMPMsg[i])
|
||||
}
|
||||
assert.Equal(t, mpegts.FixedFragmentHeader, fh)
|
||||
assert.Equal(t, goldenRTMPMsg, poped)
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
// Copyright 2021, 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 hls
|
||||
|
||||
var (
|
||||
calcFragmentHeaderQueueSize = 16
|
||||
)
|
Loading…
Reference in New Issue