blob: ff857946d641224a55e938d43ad09bd0a020278b [file] [log] [blame]
Philipp Schrader5562df72022-02-16 20:56:51 -08001// This file implements a web server that can be used by the main scouting
2// application. It can also be used in unit tests for individual components of
3// the web server.
4
5package server
6
7import (
8 "context"
9 "fmt"
10 "log"
11 "net"
12 "net/http"
13 "time"
14)
15
16type ScoutingServer interface {
17 // Add a handler for a particular path. See upstream docs for this:
18 // https://pkg.go.dev/net/http#ServeMux.Handle
19 Handle(string, http.Handler)
20 // Starts the server on the specified port. Handlers cannot be added
21 // once this function is called.
22 Start(int)
23 // Stops the server.
24 Stop()
25}
26
27// This is a collection of data we'll need to implement the ScoutingServer
28// interface.
29type scoutingServer struct {
30 mux *http.ServeMux
31 httpServer *http.Server
32 doneChan <-chan bool
33 // Denotes whether the server has ever been Start()ed before.
34 started bool
35 // Denotes whether or not the server is currently running.
36 running bool
37}
38
39// Instantiates a new ScoutingServer.
40func NewScoutingServer() ScoutingServer {
41 return &scoutingServer{
42 mux: http.NewServeMux(),
43 httpServer: nil,
44 doneChan: nil,
45 started: false,
46 running: false,
47 }
48}
49
50func (server *scoutingServer) Handle(path string, handler http.Handler) {
51 if server.started {
52 log.Fatal("Cannot add handlers once server has started.")
53 }
54 server.mux.Handle(path, handler)
55}
56
57func (server *scoutingServer) Start(port int) {
58 if server.started {
59 log.Fatal("Cannot Start() a server a second time.")
60 }
61 server.started = true
62 server.running = true
63
64 addressStr := fmt.Sprintf(":%d", port)
65 server.httpServer = &http.Server{
66 Addr: addressStr,
67 Handler: server.mux,
68 }
69
70 doneChan := make(chan bool, 1)
71 server.doneChan = doneChan
72
73 // Start the server in the background since the ListenAndServe() call
74 // blocks.
75 go func() {
76 if err := server.httpServer.ListenAndServe(); err != http.ErrServerClosed {
77 log.Fatalf("Error calling ListenAndServe(): %v", err)
78 }
79 close(doneChan)
80 }()
81
82 // Wait until the server is ready.
83 for {
84 dial, err := net.Dial("tcp", addressStr)
85 if err != nil {
86 time.Sleep(100 * time.Millisecond)
87 } else {
88 dial.Close()
89 break
90 }
91 }
92}
93
94func (server *scoutingServer) Stop() {
95 if !server.running {
96 log.Fatal("Cannot Stop() a server that's not running.")
97 }
98 server.running = false
99
100 if err := server.httpServer.Shutdown(context.Background()); err != nil {
101 log.Fatalf("Error shutting down the server: %v", err)
102 }
103 <-server.doneChan
104}