--- name: spa_handler kind: function lang: go domain: infra version: "1.0.0" purity: pure signature: "func SPAHandler(fsys fs.FS, indexFile string) http.Handler" description: "Retorna un http.Handler que sirve los archivos estaticos de un fs.FS y hace fallback a indexFile cuando el path no existe — patron SPA para React Router y similares." tags: [http, spa, frontend, embed, fileserver] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [io/fs, net/http, strings] params: - name: fsys desc: "fs.FS (tipico iofs.Sub de embed.FS) que contiene los archivos estaticos de la SPA" - name: indexFile desc: "ruta dentro de fsys del HTML de fallback (ej. \"index.html\")" output: "http.Handler que sirve archivos de fsys y hace fallback a indexFile cuando el path no existe — patron SPA para React Router" tested: true tests: - "sirve archivo estatico existente con status 200" - "ruta inexistente hace fallback a index.html con Content-Type text/html" - "raiz retorna index.html" - "path con .. retorna 400" test_file_path: "functions/infra/spa_handler_test.go" file_path: "functions/infra/spa_handler.go" --- ## Ejemplo ```go //go:embed all:frontend/dist var staticFiles embed.FS func main() { sub, _ := iofs.Sub(staticFiles, "frontend/dist") spa := SPAHandler(sub, "index.html") mux := http.NewServeMux() mux.Handle("/api/", apiRouter()) mux.Handle("/", spa) http.ListenAndServe(":8080", mux) } ``` ## Notas Funcion pura — construye el handler a partir de los parametros sin I/O. El handler resultante tiene los siguientes comportamientos: 1. Si el path contiene `..`, responde 400 (defensa contra path traversal). 2. Si el path normalizado corresponde a un archivo real en `fsys` (no directorio), lo sirve con `http.FileServer`. 3. En cualquier otro caso (ruta no existe o es directorio), sirve `indexFile` con `Content-Type: text/html; charset=utf-8` y status 200. 4. Si `indexFile` no puede leerse, responde 500. Pensado para apps que usan `//go:embed all:frontend/dist` y React Router (o cualquier router del lado del cliente). Centraliza el patron que de otro modo cada app escribiria inline.