From 47d1fca78fb3b99bba7620e382be35dad69358b8 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 12 Feb 2025 22:34:25 -0600 Subject: [PATCH 1/6] Remove unused comments/code --- static/index.html | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/static/index.html b/static/index.html index e09b26e..5041587 100644 --- a/static/index.html +++ b/static/index.html @@ -92,19 +92,6 @@ polyline.bindPopup(tagsContent+"
"+buttons); - // // Listen for popup open to attach event listeners - // polyline.on('popupopen', function(e) { - // document.querySelectorAll(".popup-btn").forEach(button => { - // button.addEventListener("click", async function(event) { - // const value = event.target.getAttribute("data-value"); - - // await fetch("/api/foo", { - // method: "POST", - // body: value - // }); - // }); - // }); - // }); } }) .catch(error => { @@ -114,11 +101,6 @@ const control = new L.Control.SimpleLocate({ position: "topleft", className: "button-locate", - afterClick: (result) => { - console.log("afterClick", result); - if (!result.geolocation) console.log("Geolocation Error"); - if (!result.orientation) console.log("Orientation Error"); - }, }).addTo(map); From 8eadd7b71e5b3ed4e7504d5f0b5ea50b36cbf24a Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 12 Feb 2025 22:54:00 -0600 Subject: [PATCH 2/6] Make sidewalk slice concurrency-safe --- main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.go b/main.go index 7c546d3..758680d 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "net/http" "runtime/debug" "strconv" + "sync" "time" "git.chandlerswift.com/chandlerswift/nau-sidewalks/sidewalk" @@ -42,6 +43,7 @@ func main() { // Decode the JSON data var sidewalks []sidewalk.Sidewalk + var sidewalksMutex sync.RWMutex if err := json.Unmarshal(sidewalk_data, &sidewalks); err != nil { log.Fatalf("Error decoding JSON: %v", err) } @@ -58,7 +60,9 @@ func main() { http.HandleFunc("GET /api/sidewalks", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + sidewalksMutex.RLock() json.NewEncoder(w).Encode(sidewalks) + sidewalksMutex.RUnlock() }) http.HandleFunc("POST /api/sidewalks/{id}", func(w http.ResponseWriter, r *http.Request) { @@ -75,8 +79,10 @@ func main() { if err != nil { http.Error(w, "Invalid condition", http.StatusBadRequest) } + sidewalksMutex.Lock() sidewalks[sidewalkID].Condition = sidewalk.Condition(condition) sidewalks[sidewalkID].LastUpdated = time.Now() + sidewalksMutex.Unlock() http.Redirect(w, r, "/", http.StatusSeeOther) }) From 51aeeb833d72c07ff18a8b72c286a0d7a3cfe35e Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 12 Feb 2025 23:06:43 -0600 Subject: [PATCH 3/6] Make DEVEL const a flag instead --- main.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 758680d..3bd84a1 100644 --- a/main.go +++ b/main.go @@ -25,13 +25,12 @@ var sidewalk_data []byte var buildInfoJSON []byte -const DEVEL = false // serve files from dev dir, not embedded, so I don't have to rebuild the app every time - func main() { + devel := flag.Bool("devel", false, "Serve from ./static instead of embedded") port := flag.Int("port", 8000, "Port to listen on") flag.Parse() - if DEVEL { + if *devel { http.Handle("GET /", http.FileServer(http.Dir("./static"))) } else { frontend, err := fs.Sub(embeddedFiles, "static") From 3b523f7742986fbca615632a0e4cf603a1f21e74 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 12 Feb 2025 23:07:04 -0600 Subject: [PATCH 4/6] Add omitted return after http.Error calls --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 3bd84a1..8ec348f 100644 --- a/main.go +++ b/main.go @@ -68,6 +68,7 @@ func main() { err := r.ParseForm() if err != nil { http.Error(w, "Could not parse form", http.StatusInternalServerError) + return } sidewalkID, err := strconv.Atoi(r.PathValue("id")) if err != nil { @@ -77,6 +78,7 @@ func main() { condition, err := strconv.Atoi(r.Form.Get("condition")) if err != nil { http.Error(w, "Invalid condition", http.StatusBadRequest) + return } sidewalksMutex.Lock() sidewalks[sidewalkID].Condition = sidewalk.Condition(condition) From 4068146c44deb3fd1226936fc4d76b75cabbddf4 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 12 Feb 2025 23:13:06 -0600 Subject: [PATCH 5/6] Add bounds checking on user-supplied input --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 8ec348f..4329bfb 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,7 @@ func main() { if err := json.Unmarshal(sidewalk_data, &sidewalks); err != nil { log.Fatalf("Error decoding JSON: %v", err) } - + sidewalksLen := len(sidewalks) for i := range sidewalks { sidewalks[i].Condition = sidewalk.Condition(rand.IntN(4)) } @@ -71,7 +71,7 @@ func main() { return } sidewalkID, err := strconv.Atoi(r.PathValue("id")) - if err != nil { + if err != nil || sidewalkID < 0 || sidewalkID >= sidewalksLen { http.Error(w, "Invalid id", http.StatusBadRequest) return } @@ -87,7 +87,7 @@ func main() { http.Redirect(w, r, "/", http.StatusSeeOther) }) - fmt.Printf("Serving on :%v\n", *port) + fmt.Printf("Serving %v sidewalks on :%v\n", &sidewalksLen, *port) panic(http.ListenAndServe(fmt.Sprintf(":%v", *port), nil)) } From b185fd18c6f51b2dd63287451d0f72a8f3d4f358 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 12 Feb 2025 23:35:26 -0600 Subject: [PATCH 6/6] Write out JSON on shutdown --- main.go | 75 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/main.go b/main.go index 4329bfb..ff42597 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,20 @@ package main import ( + "context" "embed" "encoding/json" "flag" "fmt" "io/fs" "log" - "math/rand/v2" "net/http" + "os" + "os/signal" "runtime/debug" "strconv" "sync" + "syscall" "time" "git.chandlerswift.com/chandlerswift/nau-sidewalks/sidewalk" @@ -20,16 +23,31 @@ import ( //go:embed static var embeddedFiles embed.FS -//go:embed data/sidewalks.json -var sidewalk_data []byte - var buildInfoJSON []byte func main() { devel := flag.Bool("devel", false, "Serve from ./static instead of embedded") port := flag.Int("port", 8000, "Port to listen on") + dataFile := flag.String("datafile", "", "File to read sidewalk data from (required)") flag.Parse() + if *dataFile == "" { + panic("datafile arg is required") + } + sidewalkData, err := os.ReadFile(*dataFile) + if err != nil { + panic(err) + } + + // Decode the JSON data + var sidewalks []sidewalk.Sidewalk + var sidewalksMutex sync.RWMutex + if err := json.Unmarshal(sidewalkData, &sidewalks); err != nil { + log.Fatalf("Error decoding JSON: %v", err) + } + sidewalksLen := len(sidewalks) + + // Serve static content if *devel { http.Handle("GET /", http.FileServer(http.Dir("./static"))) } else { @@ -40,17 +58,6 @@ func main() { http.Handle("GET /", http.FileServer(http.FS(frontend))) } - // Decode the JSON data - var sidewalks []sidewalk.Sidewalk - var sidewalksMutex sync.RWMutex - if err := json.Unmarshal(sidewalk_data, &sidewalks); err != nil { - log.Fatalf("Error decoding JSON: %v", err) - } - sidewalksLen := len(sidewalks) - for i := range sidewalks { - sidewalks[i].Condition = sidewalk.Condition(rand.IntN(4)) - } - initializeBuildInfoJSON() http.HandleFunc("GET /api/version", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -87,8 +94,42 @@ func main() { http.Redirect(w, r, "/", http.StatusSeeOther) }) - fmt.Printf("Serving %v sidewalks on :%v\n", &sidewalksLen, *port) - panic(http.ListenAndServe(fmt.Sprintf(":%v", *port), nil)) + server := &http.Server{Addr: fmt.Sprintf(":%v", *port)} + + go func() { + fmt.Printf("Serving %v sidewalks on :%v\n", sidewalksLen, *port) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("HTTP server error: %v", err) + } + }() + + // Listen for OS termination signals + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + <-stop // Block until signal is received + + fmt.Println("\nShutting down server...") + + // write out JSON + sidewalksMutex.RLock() + f, err := os.Create(*dataFile) + if err != nil { + panic(err) + } + defer f.Close() + err = json.NewEncoder(f).Encode(sidewalks) + if err != nil { + panic(err) + } + sidewalksMutex.RUnlock() + + // graceful shutdown + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := server.Shutdown(ctx); err != nil { + log.Fatalf("Server forced to shutdown: %v", err) + } + } func initializeBuildInfoJSON() {