package api import ( "log" "net/http" "os" "path/filepath" "strings" "time" ) // Mount static on the API mux (kept for compatibility; still serves under API port if you want) 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)) } } // NEW: serve the same static handler on its own port (frontend). 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 } if st, err := os.Stat(full); err == nil && !st.IsDir() { http.ServeFile(w, r, full) return } fallback := filepath.Join(dir, "index.html") if _, err := os.Stat(fallback); err == nil { http.ServeFile(w, r, fallback) return } http.NotFound(w, r) }) }