Create a library for serving static files
It serves files that are in a given directory, and this could be
extended to html files.
In order to validate the functionality we also added a simple
`ScoutingServer` that you can `Start()` and `Stop()`. The unit test
makes use of this. The `main` binary that ties all the components
together also makes use of this.
See the README for an overview of everything we added in this patch.
Change-Id: Id496b032d6aa70fd8502eeb347895ac52b5d1ddd
Signed-off-by: Het Satasiya <satasiyahet@gmail.com>
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
diff --git a/scouting/webserver/server/server.go b/scouting/webserver/server/server.go
new file mode 100644
index 0000000..ff85794
--- /dev/null
+++ b/scouting/webserver/server/server.go
@@ -0,0 +1,104 @@
+// 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)
+ // 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) 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
+}