package infra import ( "io/fs" "net/http" "strings" ) // SPAHandler retorna un http.Handler que sirve los archivos estáticos de fsys // y hace fallback a indexFile para cualquier ruta que no corresponda a un // archivo existente — patrón SPA para React Router y similares. // // fsys es típicamente un iofs.Sub(embed.FS, "frontend/dist"). // indexFile es la ruta dentro de fsys del HTML de fallback (ej. "index.html"). func SPAHandler(fsys fs.FS, indexFile string) http.Handler { fileServer := http.FileServer(http.FS(fsys)) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Defensa contra path traversal if strings.Contains(r.URL.Path, "..") { http.Error(w, "invalid path", http.StatusBadRequest) return } // Normalizar: quitar la barra inicial para fs.Stat path := strings.TrimPrefix(r.URL.Path, "/") if path == "" { path = "." } info, err := fs.Stat(fsys, path) if err == nil && !info.IsDir() { // El archivo existe y no es directorio: servir normalmente fileServer.ServeHTTP(w, r) return } // Fallback a indexFile (ruta no encontrada o directorio) data, err := fs.ReadFile(fsys, indexFile) if err != nil { http.Error(w, "could not read index file", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write(data) //nolint:errcheck }) }