diff --git a/Dockerfile b/Dockerfile index d3f8a91..0bb6b39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,6 @@ RUN go generate && go build -o ./out/ghostream . FROM alpine:3.12 RUN apk add ca-certificates libressl libstdc++ libgcc COPY --from=build_base /code/out/ghostream /app/ghostream -COPY --from=build_base /code/web/static /app/web/static COPY --from=build_base /usr/local/lib64/libsrt.so.1 /lib/libsrt.so.1 WORKDIR /app # 8080 for Web and Websocket, 2112 for prometheus monitoring and 9710 for SRT diff --git a/web/handler.go b/web/handler.go new file mode 100644 index 0000000..f72ab8e --- /dev/null +++ b/web/handler.go @@ -0,0 +1,86 @@ +package web + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/markbates/pkger" + "github.com/pion/webrtc/v3" + "gitlab.crans.org/nounous/ghostream/internal/monitoring" +) + +// Handle WebRTC session description exchange via POST +func viewerPostHandler(w http.ResponseWriter, r *http.Request) { + // Limit response body to 128KB + r.Body = http.MaxBytesReader(w, r.Body, 131072) + + // Decode client description + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + remoteDescription := webrtc.SessionDescription{} + if err := dec.Decode(&remoteDescription); err != nil { + http.Error(w, "The JSON WebRTC offer is malformed", http.StatusBadRequest) + return + } + + // Exchange session descriptions with WebRTC stream server + remoteSdpChan <- remoteDescription + localDescription := <-localSdpChan + + // Send server description as JSON + jsonDesc, err := json.Marshal(localDescription) + if err != nil { + http.Error(w, "An error occurred while formating response", http.StatusInternalServerError) + log.Println("An error occurred while sending session description", err) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(jsonDesc) + + // Increment monitoring + monitoring.WebSessions.Inc() +} + +func viewerGetHandler(w http.ResponseWriter, r *http.Request) { + // Render template + data := struct { + Path string + Cfg *Options + }{Path: r.URL.Path[1:], Cfg: cfg} + if err := templates.ExecuteTemplate(w, "base", data); err != nil { + log.Println(err.Error()) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Increment monitoring + monitoring.WebViewerServed.Inc() +} + +// Handle site index and viewer pages +// POST requests are used to exchange WebRTC session descriptions +func viewerHandler(w http.ResponseWriter, r *http.Request) { + // Validation on path + if validPath.FindStringSubmatch(r.URL.Path) == nil { + http.NotFound(w, r) + log.Print(r.URL.Path) + return + } + + // Route depending on HTTP method + switch r.Method { + case http.MethodGet: + viewerGetHandler(w, r) + case http.MethodPost: + viewerPostHandler(w, r) + default: + http.Error(w, "Sorry, only GET and POST methods are supported.", http.StatusBadRequest) + } +} + +func staticHandler() http.Handler { + // Set up static files server + staticFs := http.FileServer(pkger.Dir("/web/static")) + return http.StripPrefix("/static/", staticFs) +} diff --git a/web/web_test.go b/web/handler_test.go similarity index 100% rename from web/web_test.go rename to web/handler_test.go diff --git a/web/web.go b/web/web.go index d27830b..f3b5522 100644 --- a/web/web.go +++ b/web/web.go @@ -1,7 +1,6 @@ package web import ( - "encoding/json" "html/template" "io/ioutil" "log" @@ -12,7 +11,6 @@ import ( "github.com/markbates/pkger" "github.com/pion/webrtc/v3" - "gitlab.crans.org/nounous/ghostream/internal/monitoring" ) // Options holds web package configuration @@ -38,86 +36,6 @@ var ( validPath = regexp.MustCompile("^\\/[a-z0-9_-]*\\/?$") ) -// Handle WebRTC session description exchange via POST -func viewerPostHandler(w http.ResponseWriter, r *http.Request) { - // Limit response body to 128KB - r.Body = http.MaxBytesReader(w, r.Body, 131072) - - // Decode client description - dec := json.NewDecoder(r.Body) - dec.DisallowUnknownFields() - remoteDescription := webrtc.SessionDescription{} - if err := dec.Decode(&remoteDescription); err != nil { - http.Error(w, "The JSON WebRTC offer is malformed", http.StatusBadRequest) - return - } - - // Exchange session descriptions with WebRTC stream server - remoteSdpChan <- remoteDescription - localDescription := <-localSdpChan - - // Send server description as JSON - jsonDesc, err := json.Marshal(localDescription) - if err != nil { - http.Error(w, "An error occurred while formating response", http.StatusInternalServerError) - log.Println("An error occurred while sending session description", err) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(jsonDesc) - - // Increment monitoring - monitoring.WebSessions.Inc() -} - -func viewerGetHandler(w http.ResponseWriter, r *http.Request) { - // Render template - data := struct { - Path string - Cfg *Options - }{Path: r.URL.Path[1:], Cfg: cfg} - if err := templates.ExecuteTemplate(w, "base", data); err != nil { - log.Println(err.Error()) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - // Increment monitoring - monitoring.WebViewerServed.Inc() -} - -// Handle site index and viewer pages -// POST requests are used to exchange WebRTC session descriptions -func viewerHandler(w http.ResponseWriter, r *http.Request) { - // Validation on path - if validPath.FindStringSubmatch(r.URL.Path) == nil { - http.NotFound(w, r) - log.Print(r.URL.Path) - return - } - - // Route depending on HTTP method - switch r.Method { - case http.MethodGet: - viewerGetHandler(w, r) - case http.MethodPost: - viewerPostHandler(w, r) - default: - http.Error(w, "Sorry, only GET and POST methods are supported.", http.StatusBadRequest) - } -} - -// Handle static files -// We do not use http.FileServer as we do not want directory listing -func staticHandler(w http.ResponseWriter, r *http.Request) { - path := "./web/" + r.URL.Path - if f, err := os.Stat(path); err == nil && !f.IsDir() { - http.ServeFile(w, r, path) - } else { - http.NotFound(w, r) - } -} - // Load templates with pkger // templates will be packed in the compiled binary func loadTemplates() error { @@ -162,7 +80,7 @@ func Serve(rSdpChan chan webrtc.SessionDescription, lSdpChan chan webrtc.Session // Set up HTTP router and server mux := http.NewServeMux() mux.HandleFunc("/", viewerHandler) - mux.HandleFunc("/static/", staticHandler) + mux.Handle("/static/", staticHandler()) log.Printf("HTTP server listening on %s", cfg.ListenAddress) log.Fatal(http.ListenAndServe(cfg.ListenAddress, mux)) }