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/BUILD b/scouting/webserver/server/BUILD
new file mode 100644
index 0000000..7ac56db
--- /dev/null
+++ b/scouting/webserver/server/BUILD
@@ -0,0 +1,9 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "server",
+    srcs = ["server.go"],
+    importpath = "github.com/frc971/971-Robot-Code/scouting/webserver/server",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
+)
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
+}