From 57e08201abef07f00e4789badc2761055db3741b Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Sun, 31 May 2020 16:46:41 +0200 Subject: [PATCH] Split out web serving into a separate file. --- sfu.go | 186 +----------------------------------------------- webserver.go | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 185 deletions(-) create mode 100644 webserver.go diff --git a/sfu.go b/sfu.go index 38bffbb..71d45e1 100644 --- a/sfu.go +++ b/sfu.go @@ -6,25 +6,14 @@ package main import ( - "bufio" - "encoding/json" - "errors" "flag" - "fmt" - "html" - "io" "log" - "net/http" "os" "os/signal" "path/filepath" "runtime" "runtime/pprof" - "strings" "syscall" - "time" - - "github.com/gorilla/websocket" ) var httpAddr string @@ -94,183 +83,10 @@ func main() { iceFilename = filepath.Join(staticRoot, "ice-servers.json") - http.Handle("/", mungeHandler{http.FileServer(http.Dir(staticRoot))}) - http.HandleFunc("/group/", - func(w http.ResponseWriter, r *http.Request) { - mungeHeader(w) - http.ServeFile(w, r, staticRoot+"/sfu.html") - }) - http.HandleFunc("/ws", wsHandler) - http.HandleFunc("/public-groups.json", publicHandler) - http.HandleFunc("/stats", statsHandler) - go readPublicGroups() - - go func() { - server := &http.Server{ - Addr: httpAddr, - ReadTimeout: 60 * time.Second, - WriteTimeout: 30 * time.Second, - IdleTimeout: 120 * time.Second, - } - var err error - log.Printf("Listening on %v", httpAddr) - err = server.ListenAndServeTLS( - filepath.Join(dataDir, "cert.pem"), - filepath.Join(dataDir, "key.pem"), - ) - log.Fatalf("ListenAndServeTLS: %v", err) - }() + webserver() terminate := make(chan os.Signal, 1) signal.Notify(terminate, syscall.SIGINT) <-terminate } - -func mungeHeader(w http.ResponseWriter) { - w.Header().Add("Content-Security-Policy", - "connect-src ws: wss: 'self'; img-src data: 'self'; default-src 'self'") -} - -type mungeHandler struct { - h http.Handler -} - -func (h mungeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - mungeHeader(w) - h.h.ServeHTTP(w, r) -} - -func publicHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("content-type", "application/json") - w.Header().Set("cache-control", "no-cache") - - if r.Method == "HEAD" { - return - } - - g := getPublicGroups() - e := json.NewEncoder(w) - e.Encode(g) - return -} - -func getPassword() (string, string, error) { - f, err := os.Open(filepath.Join(dataDir, "passwd")) - if err != nil { - return "", "", err - } - defer f.Close() - - r := bufio.NewReader(f) - - s, err := r.ReadString('\n') - if err != nil { - return "", "", err - } - - l := strings.SplitN(strings.TrimSpace(s), ":", 2) - if len(l) != 2 { - return "", "", errors.New("couldn't parse passwords") - } - - return l[0], l[1], nil -} - -func statsHandler(w http.ResponseWriter, r *http.Request) { - bail := func() { - w.Header().Set("www-authenticate", "basic realm=\"stats\"") - http.Error(w, "Haha!", http.StatusUnauthorized) - } - - u, p, err := getPassword() - if err != nil { - log.Printf("Passwd: %v", err) - bail() - return - } - - username, password, ok := r.BasicAuth() - if !ok || username != u || password != p { - bail() - return - } - - w.Header().Set("content-type", "text/html; charset=utf-8") - w.Header().Set("cache-control", "no-cache") - if r.Method == "HEAD" { - return - } - - stats := getGroupStats() - - fmt.Fprintf(w, "\n\n") - fmt.Fprintf(w, "Stats\n") - fmt.Fprintf(w, "\n") - - printBitrate := func(w io.Writer, rate, maxRate uint64) error { - var err error - if maxRate != 0 && maxRate != ^uint64(0) { - _, err = fmt.Fprintf(w, "%v/%v", rate, maxRate) - } else { - _, err = fmt.Fprintf(w, "%v", rate) - } - return err - } - - printTrack := func(w io.Writer, t trackStats) { - fmt.Fprintf(w, "") - fmt.Fprintf(w, "") - printBitrate(w, t.bitrate, t.maxBitrate) - fmt.Fprintf(w, "") - fmt.Fprintf(w, "%d%%", - t.loss, - ) - if t.jitter > 0 { - fmt.Fprintf(w, "%v", t.jitter) - } else { - fmt.Fprintf(w, "") - } - fmt.Fprintf(w, "") - } - - for _, gs := range stats { - fmt.Fprintf(w, "

%v

\n", html.EscapeString(gs.name)) - fmt.Fprintf(w, "") - for _, cs := range gs.clients { - fmt.Fprintf(w, "\n", cs.id) - for _, up := range cs.up { - fmt.Fprintf(w, "\n", - up.id) - for _, t := range up.tracks { - printTrack(w, t) - } - } - for _, up := range cs.down { - fmt.Fprintf(w, "\n", - up.id) - for _, t := range up.tracks { - printTrack(w, t) - } - } - } - fmt.Fprintf(w, "
%v
Up%v
Down %v
\n") - } - fmt.Fprintf(w, "\n") -} - -var upgrader websocket.Upgrader - -func wsHandler(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Printf("Websocket upgrade: %v", err) - return - } - go func() { - err := startClient(conn) - if err != nil { - log.Printf("client: %v", err) - } - }() -} diff --git a/webserver.go b/webserver.go new file mode 100644 index 0000000..f9dde98 --- /dev/null +++ b/webserver.go @@ -0,0 +1,195 @@ +package main + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "html" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/gorilla/websocket" +) + +func webserver() { + http.Handle("/", mungeHandler{http.FileServer(http.Dir(staticRoot))}) + http.HandleFunc("/group/", + func(w http.ResponseWriter, r *http.Request) { + mungeHeader(w) + http.ServeFile(w, r, staticRoot+"/sfu.html") + }) + http.HandleFunc("/ws", wsHandler) + http.HandleFunc("/public-groups.json", publicHandler) + http.HandleFunc("/stats", statsHandler) + + go func() { + server := &http.Server{ + Addr: httpAddr, + ReadTimeout: 60 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + var err error + err = server.ListenAndServeTLS( + filepath.Join(dataDir, "cert.pem"), + filepath.Join(dataDir, "key.pem"), + ) + if err != nil { + log.Printf("ListenAndServeTLS: %v", err) + } + }() +} + +func mungeHeader(w http.ResponseWriter) { + w.Header().Add("Content-Security-Policy", + "connect-src ws: wss: 'self'; img-src data: 'self'; default-src 'self'") +} + +type mungeHandler struct { + h http.Handler +} + +func (h mungeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + mungeHeader(w) + h.h.ServeHTTP(w, r) +} + +func publicHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("content-type", "application/json") + w.Header().Set("cache-control", "no-cache") + + if r.Method == "HEAD" { + return + } + + g := getPublicGroups() + e := json.NewEncoder(w) + e.Encode(g) + return +} + +func getPassword() (string, string, error) { + f, err := os.Open(filepath.Join(dataDir, "passwd")) + if err != nil { + return "", "", err + } + defer f.Close() + + r := bufio.NewReader(f) + + s, err := r.ReadString('\n') + if err != nil { + return "", "", err + } + + l := strings.SplitN(strings.TrimSpace(s), ":", 2) + if len(l) != 2 { + return "", "", errors.New("couldn't parse passwords") + } + + return l[0], l[1], nil +} + +func statsHandler(w http.ResponseWriter, r *http.Request) { + bail := func() { + w.Header().Set("www-authenticate", "basic realm=\"stats\"") + http.Error(w, "Haha!", http.StatusUnauthorized) + } + + u, p, err := getPassword() + if err != nil { + log.Printf("Passwd: %v", err) + bail() + return + } + + username, password, ok := r.BasicAuth() + if !ok || username != u || password != p { + bail() + return + } + + w.Header().Set("content-type", "text/html; charset=utf-8") + w.Header().Set("cache-control", "no-cache") + if r.Method == "HEAD" { + return + } + + stats := getGroupStats() + + fmt.Fprintf(w, "\n\n") + fmt.Fprintf(w, "Stats\n") + fmt.Fprintf(w, "\n") + + printBitrate := func(w io.Writer, rate, maxRate uint64) error { + var err error + if maxRate != 0 && maxRate != ^uint64(0) { + _, err = fmt.Fprintf(w, "%v/%v", rate, maxRate) + } else { + _, err = fmt.Fprintf(w, "%v", rate) + } + return err + } + + printTrack := func(w io.Writer, t trackStats) { + fmt.Fprintf(w, "") + fmt.Fprintf(w, "") + printBitrate(w, t.bitrate, t.maxBitrate) + fmt.Fprintf(w, "") + fmt.Fprintf(w, "%d%%", + t.loss, + ) + if t.jitter > 0 { + fmt.Fprintf(w, "%v", t.jitter) + } else { + fmt.Fprintf(w, "") + } + fmt.Fprintf(w, "") + } + + for _, gs := range stats { + fmt.Fprintf(w, "

%v

\n", html.EscapeString(gs.name)) + fmt.Fprintf(w, "") + for _, cs := range gs.clients { + fmt.Fprintf(w, "\n", cs.id) + for _, up := range cs.up { + fmt.Fprintf(w, "\n", + up.id) + for _, t := range up.tracks { + printTrack(w, t) + } + } + for _, up := range cs.down { + fmt.Fprintf(w, "\n", + up.id) + for _, t := range up.tracks { + printTrack(w, t) + } + } + } + fmt.Fprintf(w, "
%v
Up%v
Down %v
\n") + } + fmt.Fprintf(w, "\n") +} + +var upgrader websocket.Upgrader + +func wsHandler(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Printf("Websocket upgrade: %v", err) + return + } + go func() { + err := startClient(conn) + if err != nil { + log.Printf("client: %v", err) + } + }() +}