blob: c21e2a11db2c4a73acfadc480ad523342c0d7c5e [file] [log] [blame]
// This file implements a web server that can be used by the main scouting
// application. It can also be used in unit tests for individual components of
// the web server.
package server
import (
"context"
"fmt"
"log"
"net"
"net/http"
"time"
)
type ScoutingServer interface {
// Add a handler for a particular path. See upstream docs for this:
// https://pkg.go.dev/net/http#ServeMux.Handle
Handle(string, http.Handler)
// Add a handler function for a particular path. See upstream docs:
// https://pkg.go.dev/net/http#ServeMux.HandleFunc
HandleFunc(string, func(http.ResponseWriter, *http.Request))
// Starts the server on the specified port. Handlers cannot be added
// once this function is called.
Start(int)
// Stops the server.
Stop()
}
// This is a collection of data we'll need to implement the ScoutingServer
// interface.
type scoutingServer struct {
mux *http.ServeMux
httpServer *http.Server
doneChan <-chan bool
// Denotes whether the server has ever been Start()ed before.
started bool
// Denotes whether or not the server is currently running.
running bool
}
// Instantiates a new ScoutingServer.
func NewScoutingServer() ScoutingServer {
return &scoutingServer{
mux: http.NewServeMux(),
httpServer: nil,
doneChan: nil,
started: false,
running: false,
}
}
func (server *scoutingServer) Handle(path string, handler http.Handler) {
if server.started {
log.Fatal("Cannot add handlers once server has started.")
}
server.mux.Handle(path, handler)
}
func (server *scoutingServer) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
if server.started {
log.Fatal("Cannot add handlers once server has started.")
}
server.mux.HandleFunc(path, handler)
}
func (server *scoutingServer) Start(port int) {
if server.started {
log.Fatal("Cannot Start() a server a second time.")
}
server.started = true
server.running = true
addressStr := fmt.Sprintf(":%d", port)
server.httpServer = &http.Server{
Addr: addressStr,
Handler: server.mux,
}
doneChan := make(chan bool, 1)
server.doneChan = doneChan
// Start the server in the background since the ListenAndServe() call
// blocks.
go func() {
if err := server.httpServer.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Error calling ListenAndServe(): %v", err)
}
close(doneChan)
}()
// Wait until the server is ready.
for {
dial, err := net.Dial("tcp", addressStr)
if err != nil {
time.Sleep(100 * time.Millisecond)
} else {
dial.Close()
break
}
}
}
func (server *scoutingServer) Stop() {
if !server.running {
log.Fatal("Cannot Stop() a server that's not running.")
}
server.running = false
if err := server.httpServer.Shutdown(context.Background()); err != nil {
log.Fatalf("Error shutting down the server: %v", err)
}
<-server.doneChan
}