Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding graceful shutdown #46

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
30 changes: 30 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package server

import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"

c "github.com/adanalife/tripbot/pkg/config/tripbot"
Expand Down Expand Up @@ -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
Expand Down
216 changes: 216 additions & 0 deletions pkg/vlc-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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]

Expand Down