From b134b927045a3b0711927bee456568538ab529b7 Mon Sep 17 00:00:00 2001 From: yusing Date: Sat, 20 Dec 2025 19:24:39 +0800 Subject: [PATCH] feat(fileserver): implement spa support; add `spa` and `index` fields to config --- internal/route/fileserver.go | 29 ++++++++++++++++++++++++++--- internal/route/route.go | 5 ++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/route/fileserver.go b/internal/route/fileserver.go index 80c531a6..e5b06281 100644 --- a/internal/route/fileserver.go +++ b/internal/route/fileserver.go @@ -2,6 +2,7 @@ package route import ( "net/http" + "os" "path" "path/filepath" @@ -27,8 +28,25 @@ type ( var _ types.FileServerRoute = (*FileServer)(nil) -func handler(root string) http.Handler { - return http.FileServer(http.Dir(root)) +func handler(root string, spa bool, index string) http.Handler { + if !spa { + return http.FileServer(http.Dir(root)) + } + indexPath := filepath.Join(root, index) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + urlPath := path.Clean(r.URL.Path) + if urlPath == "/" { + http.ServeFile(w, r, indexPath) + return + } + fullPath := filepath.Join(root, filepath.FromSlash(urlPath)) + stat, err := os.Stat(fullPath) + if err == nil && !stat.IsDir() { + http.ServeFile(w, r, fullPath) + return + } + http.ServeFile(w, r, indexPath) + }) } func NewFileServer(base *Route) (*FileServer, gperr.Error) { @@ -39,7 +57,12 @@ func NewFileServer(base *Route) (*FileServer, gperr.Error) { return nil, gperr.New("`root` must be an absolute path") } - s.handler = handler(s.Root) + if s.Index == "" { + s.Index = "/index.html" + } else if s.Index[0] != '/' { + s.Index = "/" + s.Index + } + s.handler = handler(s.Root, s.SPA, s.Index) if len(s.Middlewares) > 0 { mid, err := middleware.BuildMiddlewareFromMap(s.Alias, s.Middlewares) diff --git a/internal/route/route.go b/internal/route/route.go index bc97bc7d..4795f2fc 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -44,7 +44,10 @@ type ( Scheme route.Scheme `json:"scheme,omitempty" swaggertype:"string" enums:"http,https,tcp,udp,fileserver"` Host string `json:"host,omitempty"` Port route.Port `json:"port"` - Root string `json:"root,omitempty"` + + Root string `json:"root,omitempty"` + SPA bool `json:"spa,omitempty"` // Single-page app mode: serves index for non-existent paths + Index string `json:"index,omitempty"` // Index file to serve for single-page app mode route.HTTPConfig PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`