[L-05][medium] 后台静态页没有前端路由回退:刷新或直接打开二级页面返回 404,访问目录会列出文件 #24

Closed
opened 2026-09-30 13:56:55 +08:00 by nixevol · 1 comment
Owner

编号:L-05 严重级:medium 工作线:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) 来源:审查 A-03、P-10
依赖:无 被依赖:无

结论与统一方案

两份审查结论一致(管理后台审查判 high、传输区判 medium;有"从根路径进入"的绕过办法,取 medium)。统一方案:在 internal/httpx 新增 SPA(fsys fs.FS) http.Handler:

  1. 只处理 GET / HEAD;清洗路径后 fs.Stat,是文件就交给 FileServer。
  2. 不存在且没有扩展名(前端路由)返回 index.html,带 Cache-Control: no-cache。
  3. 有扩展名但不存在的资源(如 /assets/x.js)返回 404。
  4. 访问目录返回 index.html,关闭目录列表。

cmd/nixmsg/serve.go 的 staticFileHandler 改为调用它。

改动文件

internal/httpx/spa.go(新)、cmd/nixmsg/serve.go(staticFileHandler)。

与其他问题的交互 / 冲突说明

/api/、/mqtt、/metrics 由外层 mux 先匹配,不受影响。

验收与测试

  • GET /endpoints 返回 200 text/html;GET /assets/nope.js 返回 404;GET /assets/ 不列目录。
  • 加一个用 -tags embeddist 构建、由 Go 直出页面的 e2e 冒烟(含 page.reload())。现有 e2e 走 Vite 开发服务器,测不出这个问题。

问题明细(各区审查原文,证据含文件与行号)

以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。

[A-03] 生产静态页不回退 index.html,刷新或深链接即 404

  • 严重级:high
  • 分类:协议一致性 / 界面
  • 现象与影响:前端用 createWebHistory()(web/src/router/index.ts:4-5)。在 /endpoints、/messages 等页面按 F5、打开书签或新标签页,http.FileServer 找不到对应文件,直接返回纯文本 "404 page not found"。真实工作流不可用。
  • 证据:
func staticFileHandler() http.Handler {
	root := web.Dist()
	for _, prefix := range []string{"dist", "stub"} {
		sub, err := fs.Sub(root, prefix)
		if err != nil {
			continue
		}
		if f, err := sub.Open("index.html"); err == nil {
			_ = f.Close()
			return http.FileServer(http.FS(sub))
		}
	}
	return http.FileServer(http.FS(root))
}
  • internal/listener/routes.go:80-84,96-101 把它挂在 /。
  • e2e 的页面由 Vite 提供(playwright.config.ts:17-24;start-stack.mjs:100-103 编译时不带 embeddist),Vite 自带回退,所以测不出来。
  • 文档依据:DEVELOPMENT 4.3(约 188 行)"其余 | 后台静态页,未知前端路由回 index.html"。
  • 为何不是故意设计:DEVIATIONS 没有相关条目。
  • 解决方案:在 internal/httpx 新增 SPA(fsys fs.FS) http.Handler:
    1. 只处理 GET/HEAD;文件存在且不是目录时交给 FileServer。
    2. 无扩展名的路径返回 index.html,加 Cache-Control: no-cache。
    3. 有扩展名但不存在的资源(如 /assets/x.js)仍返回 404,并关闭目录列表。
    4. serve.go 改成一行调用它。
  • 改动文件:internal/httpx/spa.go(新)、cmd/nixmsg/serve.go(总控)。
  • 交互/冲突风险:/api/、/mqtt、/metrics 由外层 mux 先匹配,不受影响。
  • 需补测试:/endpoints 返回 200 text/html;/assets/nope.js 返回 404。加一个用 -tags embeddist 构建、由 Go 直出页面的冒烟测试,包含 page.reload()。
  • 置信度:代码阅读确定

[P-10] 后台静态页没有前端路由回退:刷新或直接打开 /overview、/endpoints 返回 404

  • 严重级:medium
  • 分类:与文档不符
  • 现象与影响:
    • 前端用的是 createWebHistory(),服务端 staticFileHandler 是裸的 http.FileServer,找不到文件就返回 404。
    • 只有从 / 进入后,前端路由才能用;刷新任何二级页面、打开收藏的链接,都得到 404。
    • 访问目录(例如 /assets/)还会列出文件清单。
    • e2e 测试走的是 Vite 开发服务器,它自带回退,所以没覆盖到内嵌静态页。
  • 证据:serve.go:350-363;web/src/router/index.ts:5;web/playwright.config.ts:18-23。
  • 文档依据:DEVELOPMENT 4.3「未知前端路由回 index.html」;PRD F21「浏览器能打开后台」。
  • 为何不是故意设计:没有记录。
  • 解决方案:包一层 FileServer:
    • 清洗路径后用 fs.Stat 查嵌入的文件系统,是文件就交给 FileServer;
    • 不存在且没有扩展名(前端路由)就返回 index.html,带 Cache-Control: no-cache;
    • 有扩展名但不存在的资源仍返回 404;
    • 访问目录也返回 index.html,顺带关掉目录列表。
  • 改动文件:cmd/nixmsg/serve.go
  • 与其他模块的交互/冲突风险:/api/... 由各自的路由处理,不受影响。
  • 需补测试:GET /endpoints 返回 200 且内容是 index.html;GET /assets/nope.js 返回 404。
  • 置信度:代码阅读确定(未用浏览器验证)

复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。

**编号**:L-05 **严重级**:medium **工作线**:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) **来源**:审查 A-03、P-10 **依赖**:无 **被依赖**:无 ### 结论与统一方案 两份审查结论一致(管理后台审查判 high、传输区判 medium;有"从根路径进入"的绕过办法,取 medium)。统一方案:在 `internal/httpx` 新增 `SPA(fsys fs.FS) http.Handler`: 1. 只处理 GET / HEAD;清洗路径后 `fs.Stat`,是文件就交给 FileServer。 2. 不存在且没有扩展名(前端路由)返回 index.html,带 `Cache-Control: no-cache`。 3. 有扩展名但不存在的资源(如 `/assets/x.js`)返回 404。 4. 访问目录返回 index.html,关闭目录列表。 `cmd/nixmsg/serve.go` 的 `staticFileHandler` 改为调用它。 ### 改动文件 `internal/httpx/spa.go`(新)、`cmd/nixmsg/serve.go`(`staticFileHandler`)。 ### 与其他问题的交互 / 冲突说明 `/api/`、`/mqtt`、`/metrics` 由外层 mux 先匹配,不受影响。 ### 验收与测试 - GET `/endpoints` 返回 200 text/html;GET `/assets/nope.js` 返回 404;GET `/assets/` 不列目录。 - 加一个用 `-tags embeddist` 构建、由 Go 直出页面的 e2e 冒烟(含 `page.reload()`)。现有 e2e 走 Vite 开发服务器,测不出这个问题。 --- ### 问题明细(各区审查原文,证据含文件与行号) > 以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。**解决方案以本 issue 上方的"结论与统一方案"为准**;原文里的方案与之不一致时,按上方执行。 #### [A-03] 生产静态页不回退 index.html,刷新或深链接即 404 - 严重级:high - 分类:协议一致性 / 界面 - 现象与影响:前端用 `createWebHistory()`(`web/src/router/index.ts:4-5`)。在 `/endpoints`、`/messages` 等页面按 F5、打开书签或新标签页,`http.FileServer` 找不到对应文件,直接返回纯文本 "404 page not found"。真实工作流不可用。 - 证据: ```350:363:e:\code\NixMsg\cmd\nixmsg\serve.go func staticFileHandler() http.Handler { root := web.Dist() for _, prefix := range []string{"dist", "stub"} { sub, err := fs.Sub(root, prefix) if err != nil { continue } if f, err := sub.Open("index.html"); err == nil { _ = f.Close() return http.FileServer(http.FS(sub)) } } return http.FileServer(http.FS(root)) } ``` - `internal/listener/routes.go:80-84,96-101` 把它挂在 `/`。 - e2e 的页面由 Vite 提供(`playwright.config.ts:17-24`;`start-stack.mjs:100-103` 编译时不带 `embeddist`),Vite 自带回退,所以测不出来。 - 文档依据:DEVELOPMENT 4.3(约 188 行)"其余 | 后台静态页,未知前端路由回 index.html"。 - 为何不是故意设计:DEVIATIONS 没有相关条目。 - 解决方案:在 `internal/httpx` 新增 `SPA(fsys fs.FS) http.Handler`: 1. 只处理 GET/HEAD;文件存在且不是目录时交给 FileServer。 2. 无扩展名的路径返回 `index.html`,加 `Cache-Control: no-cache`。 3. 有扩展名但不存在的资源(如 `/assets/x.js`)仍返回 404,并关闭目录列表。 4. `serve.go` 改成一行调用它。 - 改动文件:`internal/httpx/spa.go`(新)、`cmd/nixmsg/serve.go`(总控)。 - 交互/冲突风险:`/api/`、`/mqtt`、`/metrics` 由外层 mux 先匹配,不受影响。 - 需补测试:`/endpoints` 返回 200 text/html;`/assets/nope.js` 返回 404。加一个用 `-tags embeddist` 构建、由 Go 直出页面的冒烟测试,包含 `page.reload()`。 - 置信度:代码阅读确定 #### [P-10] 后台静态页没有前端路由回退:刷新或直接打开 /overview、/endpoints 返回 404 - **严重级**:medium - **分类**:与文档不符 - **现象与影响**: - 前端用的是 `createWebHistory()`,服务端 `staticFileHandler` 是裸的 `http.FileServer`,找不到文件就返回 404。 - 只有从 `/` 进入后,前端路由才能用;刷新任何二级页面、打开收藏的链接,都得到 404。 - 访问目录(例如 `/assets/`)还会列出文件清单。 - e2e 测试走的是 Vite 开发服务器,它自带回退,所以没覆盖到内嵌静态页。 - **证据**:`serve.go:350-363`;`web/src/router/index.ts:5`;`web/playwright.config.ts:18-23`。 - **文档依据**:DEVELOPMENT 4.3「未知前端路由回 index.html」;PRD F21「浏览器能打开后台」。 - **为何不是故意设计**:没有记录。 - **解决方案**:包一层 FileServer: - 清洗路径后用 `fs.Stat` 查嵌入的文件系统,是文件就交给 FileServer; - 不存在且没有扩展名(前端路由)就返回 index.html,带 `Cache-Control: no-cache`; - 有扩展名但不存在的资源仍返回 404; - 访问目录也返回 index.html,顺带关掉目录列表。 - **改动文件**:`cmd/nixmsg/serve.go` - **与其他模块的交互/冲突风险**:`/api/...` 由各自的路由处理,不受影响。 - **需补测试**:GET `/endpoints` 返回 200 且内容是 index.html;GET `/assets/nope.js` 返回 404。 - **置信度**:代码阅读确定(未用浏览器验证) --- <sub>复审基线:main `4059a15`(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。</sub>
nixevol added the P2-mediumlane/listenerreview-2026-09-30 labels 2026-09-30 13:56:55 +08:00
Author
Owner

已合入 origin/main 0c9b459。落地提交 7c926a0 fix: 修复 TLS ConnectionState、SPA 回退、健康检查与监听小问题 (#24)。

已合入 origin/main `0c9b459`。落地提交 `7c926a0` fix: 修复 TLS ConnectionState、SPA 回退、健康检查与监听小问题 (#24)。
Sign in to join this conversation.