From ef3fab477effc3e846e883a61fe47a9c7d9cae4d Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 17 Oct 2021 16:49:49 +0200 Subject: [PATCH] hls: use gin as HTTP router --- internal/core/api.go | 4 +- internal/core/hls_muxer.go | 77 +++++++++++++++++++++++-------------- internal/core/hls_server.go | 50 ++++++++++++++---------- 3 files changed, 80 insertions(+), 51 deletions(-) diff --git a/internal/core/api.go b/internal/core/api.go index da99966a..2a91d9b6 100644 --- a/internal/core/api.go +++ b/internal/core/api.go @@ -288,9 +288,7 @@ func newAPI( group.GET("/v1/rtmpconns/list", a.onRTMPConnsList) group.POST("/v1/rtmpconns/kick/:id", a.onRTMPConnsKick) - a.s = &http.Server{ - Handler: router, - } + a.s = &http.Server{Handler: router} go a.s.Serve(ln) diff --git a/internal/core/hls_muxer.go b/internal/core/hls_muxer.go index 5b53e7cc..aef0c14d 100644 --- a/internal/core/hls_muxer.go +++ b/internal/core/hls_muxer.go @@ -93,12 +93,17 @@ window.addEventListener('DOMContentLoaded', create); ` +type hlsMuxerResponse struct { + Status int + Header map[string]string + Body io.Reader +} + type hlsMuxerRequest struct { Dir string File string Req *http.Request - W http.ResponseWriter - Res chan io.Reader + Res chan hlsMuxerResponse } type hlsMuxerTrackIDPayloadPair struct { @@ -211,7 +216,7 @@ outer: case req := <-r.request: if isReady { - r.handleRequest(req) + req.Res <- r.handleRequest(req) } else { r.requests = append(r.requests, req) } @@ -219,7 +224,7 @@ outer: case <-innerReady: isReady = true for _, req := range r.requests { - r.handleRequest(req) + req.Res <- r.handleRequest(req) } r.requests = nil @@ -235,8 +240,7 @@ outer: r.ctxCancel() for _, req := range r.requests { - req.W.WriteHeader(http.StatusNotFound) - req.Res <- nil + req.Res <- hlsMuxerResponse{Status: http.StatusNotFound} } 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()) conf := r.path.Conf() @@ -407,48 +411,66 @@ func (r *hlsMuxer) handleRequest(req hlsMuxerRequest) { ip := net.ParseIP(tmp) if !ipEqualOrInRange(ip, conf.ReadIPs) { r.log(logger.Info, "ERR: ip '%s' not allowed", ip) - req.W.WriteHeader(http.StatusUnauthorized) - req.Res <- nil - return + return hlsMuxerResponse{Status: http.StatusUnauthorized} } } if conf.ReadUser != "" { user, pass, ok := req.Req.BasicAuth() if !ok || user != string(conf.ReadUser) || pass != string(conf.ReadPass) { - req.W.Header().Set("WWW-Authenticate", `Basic realm="rtsp-simple-server"`) - req.W.WriteHeader(http.StatusUnauthorized) - req.Res <- nil - return + return hlsMuxerResponse{ + Status: http.StatusUnauthorized, + Header: map[string]string{ + "WWW-Authenticate": `Basic realm="rtsp-simple-server"`, + }, + } } } switch { case req.File == "index.m3u8": - req.W.Header().Set("Content-Type", `application/x-mpegURL`) - req.Res <- r.muxer.PrimaryPlaylist() + return hlsMuxerResponse{ + Status: http.StatusOK, + Header: map[string]string{ + "Content-Type": `application/x-mpegURL`, + }, + Body: r.muxer.PrimaryPlaylist(), + } case req.File == "stream.m3u8": - req.W.Header().Set("Content-Type", `application/x-mpegURL`) - req.Res <- r.muxer.StreamPlaylist() + return hlsMuxerResponse{ + Status: http.StatusOK, + Header: map[string]string{ + "Content-Type": `application/x-mpegURL`, + }, + Body: r.muxer.StreamPlaylist(), + } case strings.HasSuffix(req.File, ".ts"): r := r.muxer.Segment(req.File) if r == nil { - req.W.WriteHeader(http.StatusNotFound) - req.Res <- nil - return + return hlsMuxerResponse{Status: http.StatusNotFound} } - req.W.Header().Set("Content-Type", `video/MP2T`) - req.Res <- r + return hlsMuxerResponse{ + Status: http.StatusOK, + Header: map[string]string{ + "Content-Type": `video/MP2T`, + }, + Body: r, + } 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: - req.W.WriteHeader(http.StatusNotFound) - req.Res <- nil + return hlsMuxerResponse{Status: http.StatusNotFound} } } @@ -457,8 +479,7 @@ func (r *hlsMuxer) OnRequest(req hlsMuxerRequest) { select { case r.request <- req: case <-r.ctx.Done(): - req.W.WriteHeader(http.StatusNotFound) - req.Res <- nil + req.Res <- hlsMuxerResponse{Status: http.StatusNotFound} } } diff --git a/internal/core/hls_server.go b/internal/core/hls_server.go index eae1b73b..d1c229f8 100644 --- a/internal/core/hls_server.go +++ b/internal/core/hls_server.go @@ -9,6 +9,8 @@ import ( "strings" "sync" + "github.com/gin-gonic/gin" + "github.com/aler9/rtsp-simple-server/internal/conf" "github.com/aler9/rtsp-simple-server/internal/logger" ) @@ -97,7 +99,11 @@ func (s *hlsServer) close() { func (s *hlsServer) run() { 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) outer: @@ -130,33 +136,32 @@ outer: s.pathManager.OnHLSServerSet(nil) } -// ServeHTTP implements http.Handler. -func (s *hlsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - s.Log(logger.Info, "[conn %v] %s %s", r.RemoteAddr, r.Method, r.URL.Path) +func (s *hlsServer) onRequest(ctx *gin.Context) { + s.Log(logger.Info, "[conn %v] %s %s", ctx.Request.RemoteAddr, ctx.Request.Method, ctx.Request.URL.Path) // remove leading prefix - pa := r.URL.Path[1:] + pa := ctx.Request.URL.Path[1:] - w.Header().Add("Access-Control-Allow-Origin", s.hlsAllowOrigin) - w.Header().Add("Access-Control-Allow-Credentials", "true") + ctx.Writer.Header().Add("Access-Control-Allow-Origin", s.hlsAllowOrigin) + ctx.Writer.Header().Add("Access-Control-Allow-Credentials", "true") - switch r.Method { + switch ctx.Request.Method { case http.MethodGet: case http.MethodOptions: - w.Header().Add("Access-Control-Allow-Methods", "GET, OPTIONS") - w.Header().Add("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) - w.WriteHeader(http.StatusOK) + ctx.Writer.Header().Add("Access-Control-Allow-Methods", "GET, OPTIONS") + ctx.Writer.Header().Add("Access-Control-Allow-Headers", ctx.Request.Header.Get("Access-Control-Request-Headers")) + ctx.Writer.WriteHeader(http.StatusOK) return default: - w.WriteHeader(http.StatusNotFound) + ctx.Writer.WriteHeader(http.StatusNotFound) return } switch pa { case "", "favicon.ico": - w.WriteHeader(http.StatusNotFound) + ctx.Writer.WriteHeader(http.StatusNotFound) return } @@ -168,27 +173,32 @@ func (s *hlsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { }() if fname == "" && !strings.HasSuffix(dir, "/") { - w.Header().Add("Location", "/"+dir+"/") - w.WriteHeader(http.StatusMovedPermanently) + ctx.Writer.Header().Add("Location", "/"+dir+"/") + ctx.Writer.WriteHeader(http.StatusMovedPermanently) return } dir = strings.TrimSuffix(dir, "/") - cres := make(chan io.Reader) + cres := make(chan hlsMuxerResponse) hreq := hlsMuxerRequest{ Dir: dir, File: fname, - Req: r, - W: w, + Req: ctx.Request, Res: cres, } select { case s.request <- hreq: 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():