package api import ( "log" "mime" "net/http" "os" "path/filepath" "strings" "time" ) func init() { // Ensure common types are known (some distros are sparse by default) _ = mime.AddExtensionType(".js", "application/javascript; charset=utf-8") _ = mime.AddExtensionType(".css", "text/css; charset=utf-8") _ = mime.AddExtensionType(".html", "text/html; charset=utf-8") _ = mime.AddExtensionType(".map", "application/json; charset=utf-8") } func (s *Server) MountStatic(dir string, baseURL string) { if dir == "" { return } if baseURL == "" { baseURL = "/" } s.mux.Handle(baseURL, s.staticHandler(dir, baseURL)) if !strings.HasSuffix(baseURL, "/") { s.mux.Handle(baseURL+"/", s.staticHandler(dir, baseURL)) } } func (s *Server) ListenFrontendHTTP(addr, dir, baseURL string) error { if dir == "" || addr == "" { return nil } log.Printf("frontend listening on %s (dir=%s base=%s)", addr, dir, baseURL) mx := http.NewServeMux() mx.Handle(baseURL, s.staticHandler(dir, baseURL)) if !strings.HasSuffix(baseURL, "/") { mx.Handle(baseURL+"/", s.staticHandler(dir, baseURL)) } server := &http.Server{ Addr: addr, Handler: mx, ReadHeaderTimeout: 5 * time.Second, } return server.ListenAndServe() } func (s *Server) staticHandler(dir, baseURL string) http.Handler { if baseURL == "" { baseURL = "/" } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s.secureHeaders(w) up := strings.TrimPrefix(r.URL.Path, baseURL) if up == "" || strings.HasSuffix(r.URL.Path, "/") { up = "index.html" } full := filepath.Join(dir, filepath.FromSlash(up)) if !strings.HasPrefix(filepath.Clean(full), filepath.Clean(dir)) { http.NotFound(w, r) return } // Serve file if it exists, else SPA-fallback to index.html if st, err := os.Stat(full); err == nil && !st.IsDir() { // Set Content-Type explicitly based on extension if ctype := mime.TypeByExtension(filepath.Ext(full)); ctype != "" { w.Header().Set("Content-Type", ctype) } http.ServeFile(w, r, full) return } fallback := filepath.Join(dir, "index.html") if _, err := os.Stat(fallback); err == nil { w.Header().Set("Content-Type", "text/html; charset=utf-8") http.ServeFile(w, r, fallback) return } http.NotFound(w, r) }) }