mirror of https://github.com/q191201771/lal.git
messages:
- [feat] HTTP端口复用:HTTP-FLV, HTTP-TS, HLS可使用相同的监听端口。HTTPS类似 #64 - [feat] HTTPS:支持HTTPS-TS, HLS over HTTPS(之前已支持HTTPS-FLV) #66 - [feat] WebSocket:支持WebSocket[s]-TS(之前已支持WebSocket[s]-FLV)#66 - [feat] 配置灵活。增加`default_http`。HTTP-FLV,HTTP-TS,HLS可以独立配置监听地址相关的项,也可以使用公共的`default_http` - [refactor] 重构httpflv.SubSession和httpts.SubSession的重复代码pull/78/head
parent
c0d600ea1b
commit
344a2c82b1
@ -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()
|
||||||
|
)
|
Loading…
Reference in New Issue