diff --git a/pkg/server/server.go b/pkg/server/server.go index 2f6c5697..a43b7cab 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -1,9 +1,12 @@ package server import ( + "context" "fmt" "log" "net/http" + "os" + "os/signal" "time" c "github.com/adanalife/tripbot/pkg/config/tripbot" @@ -95,6 +98,33 @@ func Start() { if err := srv.ListenAndServe(); err != nil { terrors.Fatal(err, "couldn't start server") } + + // Run our server in a goroutine so that it doesn't block. + go func() { + if err := srv.ListenAndServe(); err != nil { + terrors.Log(err, "couldn't start server") + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to wait for. + ctx, cancel := context.WithTimeout(context.Background(), wait) + defer cancel() + // Doesn't block if no connections, but will otherwise wait + // until the timeout deadline. + srv.Shutdown(ctx) + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should wait for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) } // isValidSecret returns true if the given secret matches the configured one diff --git a/pkg/vlc-server/server.go b/pkg/vlc-server/server.go index 86c5110e..5239a868 100644 --- a/pkg/vlc-server/server.go +++ b/pkg/vlc-server/server.go @@ -10,7 +10,12 @@ import ( c "github.com/adanalife/tripbot/pkg/config/vlc-server" terrors "github.com/adanalife/tripbot/pkg/errors" "github.com/adanalife/tripbot/pkg/helpers" +<<<<<<< HEAD + onscreensServer "github.com/adanalife/tripbot/pkg/onscreens-server" + "github.com/davecgh/go-spew/spew" +======= sentrynegroni "github.com/getsentry/sentry-go/negroni" +>>>>>>> origin/master "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" metrics "github.com/slok/go-http-metrics/metrics/prometheus" @@ -20,11 +25,221 @@ import ( "github.com/urfave/negroni" ) +<<<<<<< HEAD +// healthcheck URL, for tools to verify the stream is alive +func healthHandler(w http.ResponseWriter, r *http.Request) { + //TODO: rewrite this as a handler + healthCheck(w) +} + +func vlcCurrentHandler(w http.ResponseWriter, r *http.Request) { + // return the currently-playing file + fmt.Fprintf(w, currentlyPlaying()) +} + +func vlcPlayHandler(w http.ResponseWriter, r *http.Request) { + videoFile, ok := r.URL.Query()["video"] + if !ok || len(videoFile) > 1 { + //TODO: eventually this could just play instead of hard-requiring a param + http.Error(w, "417 expectation failed", http.StatusExpectationFailed) + return + } + + spew.Dump(videoFile) + playVideoFile(videoFile[0]) + + //TODO: better response + fmt.Fprintf(w, "OK") +} + +func vlcBackHandler(w http.ResponseWriter, r *http.Request) { + num, ok := r.URL.Query()["n"] + if !ok || len(num) > 1 { + back(1) + return + } + i, err := strconv.Atoi(num[0]) + if err != nil { + terrors.Log(err, "couldn't convert input to int") + http.Error(w, "422 unprocessable entity", http.StatusUnprocessableEntity) + return + } + + back(i) + + //TODO: better response + fmt.Fprintf(w, "OK") + +} + +func vlcSkipHandler(w http.ResponseWriter, r *http.Request) { + num, ok := r.URL.Query()["n"] + if !ok || len(num) > 1 { + skip(1) + return + } + i, err := strconv.Atoi(num[0]) + if err != nil { + terrors.Log(err, "couldn't convert input to int") + http.Error(w, "422 unprocessable entity", http.StatusUnprocessableEntity) + return + } + + skip(i) + + //TODO: better response + fmt.Fprintf(w, "OK") +} + +func vlcRandomHandler(w http.ResponseWriter, r *http.Request) { + // play a random file + err := PlayRandom() + if err != nil { + http.Error(w, "error playing random", http.StatusInternalServerError) + } + fmt.Fprintf(w, "OK") +} + +func onscreensFlagShowHandler(w http.ResponseWriter, r *http.Request) { + base64content, ok := r.URL.Query()["duration"] + if !ok || len(base64content) > 1 { + http.Error(w, "417 expectation failed", http.StatusExpectationFailed) + return + } + durStr, err := helpers.Base64Decode(base64content[0]) + if err != nil { + terrors.Log(err, "unable to decode string") + http.Error(w, "422 unprocessable entity", http.StatusUnprocessableEntity) + return + } + dur, err := time.ParseDuration(durStr) + if err != nil { + http.Error(w, "unable to parse duration", http.StatusInternalServerError) + return + } + onscreensServer.ShowFlag(dur) + fmt.Fprintf(w, "OK") +} + +func onscreensGpsHideHandler(w http.ResponseWriter, r *http.Request) { + onscreensServer.HideGPSImage() + fmt.Fprintf(w, "OK") +} + +func onscreensGpsShowHandler(w http.ResponseWriter, r *http.Request) { + onscreensServer.ShowGPSImage() + fmt.Fprintf(w, "OK") +} + +func onscreensTimewarpShowHandler(w http.ResponseWriter, r *http.Request) { + onscreensServer.ShowTimewarp() + fmt.Fprintf(w, "OK") +} + +func onscreensLeaderboardShowHandler(w http.ResponseWriter, r *http.Request) { + base64content, ok := r.URL.Query()["content"] + if !ok || len(base64content) > 1 { + http.Error(w, "417 expectation failed", http.StatusExpectationFailed) + return + } + spew.Dump(base64content[0]) + content, err := helpers.Base64Decode(base64content[0]) + if err != nil { + terrors.Log(err, "unable to decode string") + http.Error(w, "422 unprocessable entity", http.StatusUnprocessableEntity) + return + } + + onscreensServer.Leaderboard.Show(content) + fmt.Fprintf(w, "OK") +} + +func onscreensMiddleHideHandler(w http.ResponseWriter, r *http.Request) { + onscreensServer.MiddleText.Hide() + fmt.Fprintf(w, "OK") + +} + +func onscreensMiddleShowHandler(w http.ResponseWriter, r *http.Request) { + base64content, ok := r.URL.Query()["msg"] + if !ok || len(base64content) > 1 { + http.Error(w, "417 expectation failed", http.StatusExpectationFailed) + return + } + msg, err := helpers.Base64Decode(base64content[0]) + if err != nil { + terrors.Log(err, "unable to decode string") + http.Error(w, "422 unprocessable entity", http.StatusUnprocessableEntity) + return + } + onscreensServer.MiddleText.Show(msg) + fmt.Fprintf(w, "OK") +} + +func faviconHandler(w http.ResponseWriter, r *http.Request) { + // // return a favicon if anyone asks for one + //} else if r.URL.Path == "/favicon.ico" { + http.ServeFile(w, r, "assets/favicon.ico") +} + +//TODO: use more StatusExpectationFailed instead of http.StatusUnprocessableEntity +func catchAllHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + http.Error(w, "404 not found", http.StatusNotFound) + log.Println("someone tried hitting", r.URL.Path) + return + + // someone tried a PUT or a DELETE or something + default: + fmt.Fprintf(w, "Only GET methods are supported.\n") + } +} + +======= +>>>>>>> origin/master // Start starts the web server func Start() { log.Println("Starting VLC web server on host", c.Conf.VlcServerHost) r := mux.NewRouter() +<<<<<<< HEAD + + // add the prometheus middleware + r.Use(helpers.PrometheusMiddleware) + // make prometheus metrics available + r.Path("/metrics").Handler(promhttp.Handler()) + + r.HandleFunc("/health", healthHandler).Methods("GET") + + // vlc endpoints + //TODO: consider refactoring into a subrouter + r.HandleFunc("/vlc/current", vlcCurrentHandler).Methods("GET") + r.HandleFunc("/vlc/play", vlcPlayHandler).Methods("GET") + r.HandleFunc("/vlc/back", vlcBackHandler).Methods("GET") + r.HandleFunc("/vlc/skip", vlcSkipHandler).Methods("GET") + r.HandleFunc("/vlc/random", vlcRandomHandler).Methods("GET") + + // onscreen endpoints + //TODO: consider refactoring into a subrouter + r.HandleFunc("/onscreens/flag/show", onscreensFlagShowHandler).Methods("GET") + r.HandleFunc("/onscreens/gps/hide", onscreensGpsHideHandler).Methods("GET") + r.HandleFunc("/onscreens/gps/show", onscreensGpsShowHandler).Methods("GET") + r.HandleFunc("/onscreens/timewarp/show", onscreensTimewarpShowHandler).Methods("GET") + r.HandleFunc("/onscreens/leaderboard/show", onscreensLeaderboardShowHandler).Methods("GET") + r.HandleFunc("/onscreens/middle/hide", onscreensMiddleHideHandler).Methods("GET") + r.HandleFunc("/onscreens/middle/show", onscreensMiddleShowHandler).Methods("GET") + + //TODO: refactor into static serving + r.HandleFunc("/favicon.ico", faviconHandler).Methods("GET") + + //TODO: update to be proper catchall(?) + // r.PathPrefix("/").Handler(catchAllHandler) + r.HandleFunc("/", catchAllHandler) + http.Handle("/", r) + + // ListenAndServe() wants a port in the format ":NUM" +======= // healthcheck endpoints //TODO: handle HEAD requests here too @@ -92,6 +307,7 @@ func Start() { // attaching routes to handler happens last app.UseHandler(r) +>>>>>>> origin/master //TODO: error if there's no colon to split on port := strings.Split(c.Conf.VlcServerHost, ":")[1]