blob: c21e2a11db2c4a73acfadc480ad523342c0d7c5e [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)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080020 // Add a handler function for a particular path. See upstream docs:
21 // https://pkg.go.dev/net/http#ServeMux.HandleFunc
22 HandleFunc(string, func(http.ResponseWriter, *http.Request))
Philipp Schrader5562df72022-02-16 20:56:51 -080023 // Starts the server on the specified port. Handlers cannot be added
24 // once this function is called.
25 Start(int)
26 // Stops the server.
27 Stop()
28}
29
30// This is a collection of data we'll need to implement the ScoutingServer
31// interface.
32type scoutingServer struct {
33 mux *http.ServeMux
34 httpServer *http.Server
35 doneChan <-chan bool
36 // Denotes whether the server has ever been Start()ed before.
37 started bool
38 // Denotes whether or not the server is currently running.
39 running bool
40}
41
42// Instantiates a new ScoutingServer.
43func NewScoutingServer() ScoutingServer {
44 return &scoutingServer{
45 mux: http.NewServeMux(),
46 httpServer: nil,
47 doneChan: nil,
48 started: false,
49 running: false,
50 }
51}
52
53func (server *scoutingServer) Handle(path string, handler http.Handler) {
54 if server.started {
55 log.Fatal("Cannot add handlers once server has started.")
56 }
57 server.mux.Handle(path, handler)
58}
59
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080060func (server *scoutingServer) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
61 if server.started {
62 log.Fatal("Cannot add handlers once server has started.")
63 }
64 server.mux.HandleFunc(path, handler)
65}
66
Philipp Schrader5562df72022-02-16 20:56:51 -080067func (server *scoutingServer) Start(port int) {
68 if server.started {
69 log.Fatal("Cannot Start() a server a second time.")
70 }
71 server.started = true
72 server.running = true
73
74 addressStr := fmt.Sprintf(":%d", port)
75 server.httpServer = &http.Server{
76 Addr: addressStr,
77 Handler: server.mux,
78 }
79
80 doneChan := make(chan bool, 1)
81 server.doneChan = doneChan
82
83 // Start the server in the background since the ListenAndServe() call
84 // blocks.
85 go func() {
86 if err := server.httpServer.ListenAndServe(); err != http.ErrServerClosed {
87 log.Fatalf("Error calling ListenAndServe(): %v", err)
88 }
89 close(doneChan)
90 }()
91
92 // Wait until the server is ready.
93 for {
94 dial, err := net.Dial("tcp", addressStr)
95 if err != nil {
96 time.Sleep(100 * time.Millisecond)
97 } else {
98 dial.Close()
99 break
100 }
101 }
102}
103
104func (server *scoutingServer) Stop() {
105 if !server.running {
106 log.Fatal("Cannot Stop() a server that's not running.")
107 }
108 server.running = false
109
110 if err := server.httpServer.Shutdown(context.Background()); err != nil {
111 log.Fatalf("Error shutting down the server: %v", err)
112 }
113 <-server.doneChan
114}