feat(deploy): M11 单二进制装配(cmd/cyrene 全链路 wire + go:embed 前端 + 外置 dist 覆盖 + 优雅关闭 + SPA 静态服务);前端 build 拷到 internal/webui/dist
This commit is contained in:
1 parent
f43eb17030
commit
a7feaeb2cb
7 files changed
+745
-1
No files matched your search
@@ -0,0 +1,47 @@
|
||||
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/")
|
||||
}
|
||||
Reference in new issue
Block a user