主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
59 lines
1.7 KiB
Go
59 lines
1.7 KiB
Go
package gateway
|
||
|
||
import (
|
||
"net/http"
|
||
"path"
|
||
"strings"
|
||
)
|
||
|
||
// spa 用 SPA 文件服务器包裹 API mux。/api/ 与 /ws/
|
||
// 之下的请求由 API handler 服务;其他所有路径都由已配置的
|
||
// 前端文件系统服务,并回退到 index.html,使客户端路由(例如
|
||
// /chat)能在单页应用中解析。
|
||
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) || s.isWebhookRoute(r.URL.Path) {
|
||
api.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
fileServer.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
|
||
// isWebhookRoute 报告 path 是否是已注册的外部 webhook 回调
|
||
// 路由。这类路径必须抵达 API mux(webhook handler 挂载之处)
|
||
// 而不是 SPA 文件回退,使入站回调得到服务。
|
||
func (s *Server) isWebhookRoute(p string) bool {
|
||
if len(s.webhooks) == 0 {
|
||
return false
|
||
}
|
||
_, ok := s.webhooks[p]
|
||
return ok
|
||
}
|
||
|
||
// spaFileServer 提供静态资产,对不是内嵌/外部 dist 中真实
|
||
// 文件的路径回退到 index.html。
|
||
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 报告 path 是否属于网关拥有的 REST/WS API 表面,
|
||
// SPA handler 必须对之不作干预。
|
||
func isAPIRoute(p string) bool {
|
||
return strings.HasPrefix(p, "/api/") || strings.HasPrefix(p, "/ws/")
|
||
}
|