diff --git a/cmd/oikos/main.go b/cmd/oikos/main.go index 55ebf1c..573ffbe 100644 --- a/cmd/oikos/main.go +++ b/cmd/oikos/main.go @@ -1,14 +1,17 @@ package main import ( + "bytes" "context" "fmt" + "io" "log/slog" "net/http" "os" "os/signal" "strings" "syscall" + "time" "github.com/dtoro/oikos/internal/config" "github.com/dtoro/oikos/internal/db" @@ -24,30 +27,38 @@ import ( // uiHandler serves the control-room SPA from assets embedded at build time // (web/embed.go), with SPA fallback to index.html. Requests arrive as /ui/*; -// the /ui prefix is stripped to index into the embedded dist/ tree. +// the /ui prefix is stripped to index into the embedded dist/ tree. Files are +// written via http.ServeContent (not http.FileServer) to avoid its +// index.html -> "./" canonical redirect, which loops for /ui/. func uiHandler() http.Handler { dist, err := web.DistFS() if err != nil { slog.Warn("ui: embedded assets unavailable", "error", err) return http.NotFoundHandler() } - fileServer := http.FileServer(http.FS(dist)) - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - path := strings.TrimPrefix(strings.TrimPrefix(r.URL.Path, "/ui"), "/") - if path == "" { - path = "index.html" + serve := func(w http.ResponseWriter, r *http.Request, name string) bool { + f, err := dist.Open(name) + if err != nil { + return false } - if f, err := dist.Open(path); err == nil { - f.Close() - r.URL.Path = "/" + path - fileServer.ServeHTTP(w, r) + defer f.Close() + data, err := io.ReadAll(f) + if err != nil { + return false + } + http.ServeContent(w, r, name, time.Time{}, bytes.NewReader(data)) + return true + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + name := strings.TrimPrefix(strings.TrimPrefix(r.URL.Path, "/ui"), "/") + if name == "" { + name = "index.html" + } + if serve(w, r, name) { return } // SPA fallback: serve index.html for unknown client-side routes. - if f, err := dist.Open("index.html"); err == nil { - f.Close() - r.URL.Path = "/index.html" - fileServer.ServeHTTP(w, r) + if serve(w, r, "index.html") { return } http.NotFound(w, r)