Browse Source

hls: use gin as HTTP router

pull/666/head
aler9 5 years ago
parent
commit
ef3fab477e
  1. 4
      internal/core/api.go
  2. 77
      internal/core/hls_muxer.go
  3. 50
      internal/core/hls_server.go

4
internal/core/api.go

@ -288,9 +288,7 @@ func newAPI(
group.GET("/v1/rtmpconns/list", a.onRTMPConnsList) group.GET("/v1/rtmpconns/list", a.onRTMPConnsList)
group.POST("/v1/rtmpconns/kick/:id", a.onRTMPConnsKick) group.POST("/v1/rtmpconns/kick/:id", a.onRTMPConnsKick)
a.s = &http.Server{ a.s = &http.Server{Handler: router}
Handler: router,
}
go a.s.Serve(ln) go a.s.Serve(ln)

77
internal/core/hls_muxer.go

@ -93,12 +93,17 @@ window.addEventListener('DOMContentLoaded', create);
</html> </html>
` `
type hlsMuxerResponse struct {
Status int
Header map[string]string
Body io.Reader
}
type hlsMuxerRequest struct { type hlsMuxerRequest struct {
Dir string Dir string
File string File string
Req *http.Request Req *http.Request
W http.ResponseWriter Res chan hlsMuxerResponse
Res chan io.Reader
} }
type hlsMuxerTrackIDPayloadPair struct { type hlsMuxerTrackIDPayloadPair struct {
@ -211,7 +216,7 @@ outer:
case req := <-r.request: case req := <-r.request:
if isReady { if isReady {
r.handleRequest(req) req.Res <- r.handleRequest(req)
} else { } else {
r.requests = append(r.requests, req) r.requests = append(r.requests, req)
} }
@ -219,7 +224,7 @@ outer:
case <-innerReady: case <-innerReady:
isReady = true isReady = true
for _, req := range r.requests { for _, req := range r.requests {
r.handleRequest(req) req.Res <- r.handleRequest(req)
} }
r.requests = nil r.requests = nil
@ -235,8 +240,7 @@ outer:
r.ctxCancel() r.ctxCancel()
for _, req := range r.requests { for _, req := range r.requests {
req.W.WriteHeader(http.StatusNotFound) req.Res <- hlsMuxerResponse{Status: http.StatusNotFound}
req.Res <- nil
} }
r.parent.OnMuxerClose(r) r.parent.OnMuxerClose(r)
@ -397,7 +401,7 @@ func (r *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
} }
} }
func (r *hlsMuxer) handleRequest(req hlsMuxerRequest) { func (r *hlsMuxer) handleRequest(req hlsMuxerRequest) hlsMuxerResponse {
atomic.StoreInt64(r.lastRequestTime, time.Now().Unix()) atomic.StoreInt64(r.lastRequestTime, time.Now().Unix())
conf := r.path.Conf() conf := r.path.Conf()
@ -407,48 +411,66 @@ func (r *hlsMuxer) handleRequest(req hlsMuxerRequest) {
ip := net.ParseIP(tmp) ip := net.ParseIP(tmp)
if !ipEqualOrInRange(ip, conf.ReadIPs) { if !ipEqualOrInRange(ip, conf.ReadIPs) {
r.log(logger.Info, "ERR: ip '%s' not allowed", ip) r.log(logger.Info, "ERR: ip '%s' not allowed", ip)
req.W.WriteHeader(http.StatusUnauthorized) return hlsMuxerResponse{Status: http.StatusUnauthorized}
req.Res <- nil
return
} }
} }
if conf.ReadUser != "" { if conf.ReadUser != "" {
user, pass, ok := req.Req.BasicAuth() user, pass, ok := req.Req.BasicAuth()
if !ok || user != string(conf.ReadUser) || pass != string(conf.ReadPass) { if !ok || user != string(conf.ReadUser) || pass != string(conf.ReadPass) {
req.W.Header().Set("WWW-Authenticate", `Basic realm="rtsp-simple-server"`) return hlsMuxerResponse{
req.W.WriteHeader(http.StatusUnauthorized) Status: http.StatusUnauthorized,
req.Res <- nil Header: map[string]string{
return "WWW-Authenticate": `Basic realm="rtsp-simple-server"`,
},
}
} }
} }
switch { switch {
case req.File == "index.m3u8": case req.File == "index.m3u8":
req.W.Header().Set("Content-Type", `application/x-mpegURL`) return hlsMuxerResponse{
req.Res <- r.muxer.PrimaryPlaylist() Status: http.StatusOK,
Header: map[string]string{
"Content-Type": `application/x-mpegURL`,
},
Body: r.muxer.PrimaryPlaylist(),
}
case req.File == "stream.m3u8": case req.File == "stream.m3u8":
req.W.Header().Set("Content-Type", `application/x-mpegURL`) return hlsMuxerResponse{
req.Res <- r.muxer.StreamPlaylist() Status: http.StatusOK,
Header: map[string]string{
"Content-Type": `application/x-mpegURL`,
},
Body: r.muxer.StreamPlaylist(),
}
case strings.HasSuffix(req.File, ".ts"): case strings.HasSuffix(req.File, ".ts"):
r := r.muxer.Segment(req.File) r := r.muxer.Segment(req.File)
if r == nil { if r == nil {
req.W.WriteHeader(http.StatusNotFound) return hlsMuxerResponse{Status: http.StatusNotFound}
req.Res <- nil
return
} }
req.W.Header().Set("Content-Type", `video/MP2T`) return hlsMuxerResponse{
req.Res <- r Status: http.StatusOK,
Header: map[string]string{
"Content-Type": `video/MP2T`,
},
Body: r,
}
case req.File == "": case req.File == "":
req.Res <- bytes.NewReader([]byte(index)) return hlsMuxerResponse{
Status: http.StatusOK,
Header: map[string]string{
"Content-Type": `text/html`,
},
Body: bytes.NewReader([]byte(index)),
}
default: default:
req.W.WriteHeader(http.StatusNotFound) return hlsMuxerResponse{Status: http.StatusNotFound}
req.Res <- nil
} }
} }
@ -457,8 +479,7 @@ func (r *hlsMuxer) OnRequest(req hlsMuxerRequest) {
select { select {
case r.request <- req: case r.request <- req:
case <-r.ctx.Done(): case <-r.ctx.Done():
req.W.WriteHeader(http.StatusNotFound) req.Res <- hlsMuxerResponse{Status: http.StatusNotFound}
req.Res <- nil
} }
} }

50
internal/core/hls_server.go

@ -9,6 +9,8 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/gin-gonic/gin"
"github.com/aler9/rtsp-simple-server/internal/conf" "github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/logger" "github.com/aler9/rtsp-simple-server/internal/logger"
) )
@ -97,7 +99,11 @@ func (s *hlsServer) close() {
func (s *hlsServer) run() { func (s *hlsServer) run() {
defer s.wg.Done() defer s.wg.Done()
hs := &http.Server{Handler: s} gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.NoRoute(s.onRequest)
hs := &http.Server{Handler: router}
go hs.Serve(s.ln) go hs.Serve(s.ln)
outer: outer:
@ -130,33 +136,32 @@ outer:
s.pathManager.OnHLSServerSet(nil) s.pathManager.OnHLSServerSet(nil)
} }
// ServeHTTP implements http.Handler. func (s *hlsServer) onRequest(ctx *gin.Context) {
func (s *hlsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.Log(logger.Info, "[conn %v] %s %s", ctx.Request.RemoteAddr, ctx.Request.Method, ctx.Request.URL.Path)
s.Log(logger.Info, "[conn %v] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
// remove leading prefix // remove leading prefix
pa := r.URL.Path[1:] pa := ctx.Request.URL.Path[1:]
w.Header().Add("Access-Control-Allow-Origin", s.hlsAllowOrigin) ctx.Writer.Header().Add("Access-Control-Allow-Origin", s.hlsAllowOrigin)
w.Header().Add("Access-Control-Allow-Credentials", "true") ctx.Writer.Header().Add("Access-Control-Allow-Credentials", "true")
switch r.Method { switch ctx.Request.Method {
case http.MethodGet: case http.MethodGet:
case http.MethodOptions: case http.MethodOptions:
w.Header().Add("Access-Control-Allow-Methods", "GET, OPTIONS") ctx.Writer.Header().Add("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Add("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) ctx.Writer.Header().Add("Access-Control-Allow-Headers", ctx.Request.Header.Get("Access-Control-Request-Headers"))
w.WriteHeader(http.StatusOK) ctx.Writer.WriteHeader(http.StatusOK)
return return
default: default:
w.WriteHeader(http.StatusNotFound) ctx.Writer.WriteHeader(http.StatusNotFound)
return return
} }
switch pa { switch pa {
case "", "favicon.ico": case "", "favicon.ico":
w.WriteHeader(http.StatusNotFound) ctx.Writer.WriteHeader(http.StatusNotFound)
return return
} }
@ -168,27 +173,32 @@ func (s *hlsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}() }()
if fname == "" && !strings.HasSuffix(dir, "/") { if fname == "" && !strings.HasSuffix(dir, "/") {
w.Header().Add("Location", "/"+dir+"/") ctx.Writer.Header().Add("Location", "/"+dir+"/")
w.WriteHeader(http.StatusMovedPermanently) ctx.Writer.WriteHeader(http.StatusMovedPermanently)
return return
} }
dir = strings.TrimSuffix(dir, "/") dir = strings.TrimSuffix(dir, "/")
cres := make(chan io.Reader) cres := make(chan hlsMuxerResponse)
hreq := hlsMuxerRequest{ hreq := hlsMuxerRequest{
Dir: dir, Dir: dir,
File: fname, File: fname,
Req: r, Req: ctx.Request,
W: w,
Res: cres, Res: cres,
} }
select { select {
case s.request <- hreq: case s.request <- hreq:
res := <-cres res := <-cres
if res != nil {
io.Copy(w, res) for k, v := range res.Header {
ctx.Writer.Header().Set(k, v)
}
ctx.Writer.WriteHeader(res.Status)
if res.Body != nil {
io.Copy(ctx.Writer, res.Body)
} }
case <-s.ctx.Done(): case <-s.ctx.Done():

Loading…
Cancel
Save