Files
NixMsg/internal/httpx/spa.go
T

58 lines
1.3 KiB
Go

package httpx
import (
"io"
"io/fs"
"net/http"
"path"
"strings"
)
// SPA 提供后台静态页:存在的文件原样返回;无扩展名的前端路由回退 index.html;
// 有扩展名但不存在返回 404;访问目录不列清单,同样回退 index.html。
func SPA(fsys fs.FS) http.Handler {
files := http.FileServer(http.FS(fsys))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
cleaned := path.Clean("/" + r.URL.Path)
rel := strings.TrimPrefix(cleaned, "/")
if rel == "" {
rel = "."
}
fi, err := fs.Stat(fsys, rel)
if err == nil && !fi.IsDir() {
files.ServeHTTP(w, r)
return
}
if path.Ext(cleaned) != "" {
http.NotFound(w, r)
return
}
serveIndex(w, r, fsys)
})
}
func serveIndex(w http.ResponseWriter, r *http.Request, fsys fs.FS) {
f, err := fsys.Open("index.html")
if err != nil {
http.NotFound(w, r)
return
}
defer func() { _ = f.Close() }()
st, err := f.Stat()
if err != nil || st.IsDir() {
http.NotFound(w, r)
return
}
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
_, _ = io.Copy(w, f)
}