Files
Cyrene_v2/internal/services/gateway/static.go
T

48 lines
1.4 KiB
Go

package gateway
import (
"net/http"
"path"
"strings"
)
// spa wraps the API mux with the SPA file server. Requests under /api/ and /ws/
// are served by the API handler; every other path is served from the configured
// frontend filesystem with a fallback to index.html so client-side routes (e.g.
// /chat) resolve in a single-page app.
func (s *Server) spa(api http.Handler) http.Handler {
fileServer := s.spaFileServer()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isAPIRoute(r.URL.Path) {
api.ServeHTTP(w, r)
return
}
fileServer.ServeHTTP(w, r)
})
}
// spaFileServer serves static assets, falling back to index.html for paths that
// are not real files in the embedded/external dist.
func (s *Server) spaFileServer() http.Handler {
fsrv := http.FileServer(http.FS(s.frontend))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rel := strings.TrimPrefix(path.Clean(r.URL.Path), "/")
if rel != "" {
if f, err := s.frontend.Open(rel); err == nil {
_ = f.Close()
fsrv.ServeHTTP(w, r)
return
}
}
r2 := r.Clone(r.Context())
r2.URL.Path = "/"
fsrv.ServeHTTP(w, r2)
})
}
// isAPIRoute reports whether path belongs to the REST/WS API surface the
// gateway owns, which the SPA handler must leave untouched.
func isAPIRoute(p string) bool {
return strings.HasPrefix(p, "/api/") || strings.HasPrefix(p, "/ws/")
}