补齐 Pingora 网关的 SPA allowlist 并关闭 SPA 路由漂移待办
Project CI / AI game creator shell Rust crates (push) Successful in 1m16s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m52s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m16s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m52s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
- Pingora MAIN_SPA_PATHS 从 5 条补到 12 条(补 /components、/design-system、/games、/games/detail、/games/mine、/games/play、/games/publish),与前端路由源和 nginx 三份模板逐条一致 - 路由对照矩阵新增 games_spa_fallback(/games/detail → static/web/spa_fallback)并同步 Pingora 试点文档的 SPA allowlist 表,让 cargo test -p pingora-gateway matches_nginx_route_parity_matrix 真正覆盖这条 - 门禁与验证:check:nginx-spa-routes OK(12 路由 / 3 模板)、check:pingora-route-parity OK(22 路由)、网关全量 38 passed、check:production-ops OK、cargo fmt --all --check OK;变异验证:临时拿掉 /games/detail 后矩阵用例以 static mode mismatch 变红;dev 与生产 5 条 /games* 深链实测 200 - 关闭【待办】主站SPA路由白名单与前端路由源不一致 并写入关闭记录;另立【待办】Pingora网关缺发行网关路由(三份 nginx 模板都有 /games/game_<32hex>/ → 发行网关,Pingora 缺这条代理,切流后会 404)
This commit is contained in:
@@ -297,6 +297,26 @@
|
||||
},
|
||||
"docs": ["主站 SPA allowlist", "失败回退 `/index.html`"]
|
||||
},
|
||||
{
|
||||
"id": "games_spa_fallback",
|
||||
"samplePath": "/games/detail",
|
||||
"expect": {
|
||||
"kind": "static",
|
||||
"root": "web",
|
||||
"mode": "spa_fallback"
|
||||
},
|
||||
"nginx": {
|
||||
"production": [
|
||||
"# BEGIN GENARRATIVE MAIN SPA ROUTES",
|
||||
"try_files $uri /index.html =404;"
|
||||
],
|
||||
"development": [
|
||||
"# BEGIN GENARRATIVE MAIN SPA ROUTES",
|
||||
"try_files $uri /index.html =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["主站 SPA allowlist", "游戏目录 / 详情 / 游玩 / 我的 / 发布深链"]
|
||||
},
|
||||
{
|
||||
"id": "web_spa_case_trailing_slash",
|
||||
"samplePath": "/PROJECT/",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# 【待办】Pingora 网关缺"发行网关"路由(Nginx 已有)
|
||||
|
||||
更新时间:`2026-09-29`
|
||||
|
||||
状态:开放。这是修「主站 SPA allowlist 漂移」时**顺手查出来的另一处漂移**:三份 Nginx 模板都把 `/games/game_<32 位十六进制 id>/…` 映射到发行网关,Pingora 网关没有对应分支。两个路由 parity 门禁现在都是绿的(`check:nginx-spa-routes` OK 12 路由、`check:pingora-route-parity` OK 22 路由),所以这条**不是**它们在报的问题,而是路由对照矩阵本身没覆盖的空白。
|
||||
|
||||
## 现象(2026-09-29 只读核对)
|
||||
|
||||
- Nginx:`deploy/nginx/genarrative.conf:205`、`deploy/nginx/genarrative-dev-http.conf:193`、`deploy/container/nginx.conf:150` 都有
|
||||
`location ~ "^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$"`,
|
||||
内部 `proxy_set_header Cookie "";` + `proxy_pass http://genarrative_api/api/game-distribution/releases/$game_id$game_path;`。
|
||||
- Pingora:`server-rs/crates/pingora-gateway/src/main.rs` 的 `classify_path` 没有任何 `/games/...` 分支(`MAIN_SPA_PATHS` 里也**不应该**有它),于是落到末尾的 `Static { root: Web, mode: Exact }` —— 真机上就是 404。
|
||||
- 矩阵:`deploy/pingora/nginx-route-parity.matrix.json` 没有这条用例,所以 `cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 覆盖不到。
|
||||
|
||||
## 影响
|
||||
|
||||
Pingora 目前只监听 `127.0.0.1:18081`(shadow / direct 试点),公网入口仍是 Nginx,**当前没有用户可见故障**。但试点文档写明「切流前必须与 Nginx 逐条对齐」;一旦把公网入口切到 Pingora,**所有已公开游戏的在线游玩入口 `/games/game_<id>/` 会 404**,而目录与详情仍然 200 —— 必须从详情页点「立即玩」才会暴露,属于很难第一时间发现的缺口。
|
||||
|
||||
## 修法建议
|
||||
|
||||
1. 网关加一个独立的"发行网关代理"决策(不要塞进 SPA allowlist):匹配 `^/games/game_[0-9a-f]{32}(?:/.*)?$`,代理到 api 上游的 `/api/game-distribution/releases/<game_id><game_path>`,转发时**清空 Cookie**(与 Nginx 同口径:发行内容不读账号凭证),其余响应头(CSP / nosniff / CORP)仍由 api-server 出。
|
||||
2. 矩阵补 `games_release_gateway` 用例并把三份模板的同名片段写进 `nginx` 字段;因为这是「路径重写 + 清 Cookie」,需要给矩阵加一种表达(新增 `expect.kind`,或给 `proxy` 增加可选的重写/头字段),让 `check:pingora-route-parity` 能逐条比对。
|
||||
3. 验证:`cargo test -p pingora-gateway matches_nginx_route_parity_matrix` + `check:pingora-gateway-smoke`(本机需先设 `OPENSSL_CONF`,见 pitfalls);再加一条反例断言 `/games/not-a-game-id/` 仍是真实 404。
|
||||
|
||||
## 关闭条件
|
||||
|
||||
- 矩阵里有该路由的用例,`npm run check:pingora-route-parity` 与 `npm run check:nginx-spa-routes` 都绿,`cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 逐条断言通过。
|
||||
- `check:pingora-gateway-smoke` 在真实网关进程上证明 `/games/game_<32hex>/…` 被代理到发行网关、且上游拿不到 Cookie;`/games/not-a-game-id/` 仍返回 404。
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
更新时间:`2026-09-24`
|
||||
|
||||
状态:部分关闭。共享组件展示页(`/components`、`/design-system`)的缺失已在本轮补齐;剩余 5 条 `/games*` 路由属冻结的游戏分发系列,`npm run check:nginx-spa-routes` 与 `npm run check:pingora-route-parity` 仍为红灯。
|
||||
状态:**已关闭(2026-09-29)**。两个门禁都已转绿,关闭证据见文末「关闭记录(2026-09-29)」。
|
||||
|
||||
## 现象(2026-09-24 实跑)
|
||||
|
||||
@@ -39,4 +39,16 @@
|
||||
|
||||
1. `npm run check:nginx-spa-routes` 与 `npm run check:pingora-route-parity` 通过,三份模板的 allowlist 集合与前端路由源逐条一致。
|
||||
2. 大小写与尾部斜杠容忍、`/creation/not-exist` 等未知路径仍返回 404 的判据不被放宽。
|
||||
3. 游戏部分补齐前,本文件保持打开,不能把「门禁仍红」当成已收口。
|
||||
3. 游戏部分补齐前,本文件保持打开,不能把「门禁仍红」当成已收口。
|
||||
|
||||
## 关闭记录(2026-09-29)
|
||||
|
||||
三条关闭条件逐条复验通过:
|
||||
|
||||
1. **两个门禁都绿**:`npm run check:nginx-spa-routes` → `OK (12 SPA routes, 3 Nginx templates)`;`npm run check:pingora-route-parity` → `OK (22 routes)`。nginx 侧那 5 条 `/games*` 是随 `87e52860a`(游戏发行入口改为平台同源路径)补进三份模板的;这轮把**漏掉的 Pingora 侧**补齐——`server-rs/crates/pingora-gateway/src/main.rs` 的 `MAIN_SPA_PATHS` 从 5 条补到 12 条(`/components`、`/design-system`、`/games`、`/games/detail`、`/games/mine`、`/games/play`、`/games/publish` 与原有 5 条),并同步 `docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md` 的 SPA allowlist 表。
|
||||
2. **判据没有被放宽**:`check-nginx-spa-routes.mjs` 仍在断言大小写不敏感、允许一个尾部斜杠、`/creation/not-exist` 等未知路径必须只读真实静态文件并返回 404;`is_main_spa_path()` 仍是精确(大小写不敏感 + 去一个尾部斜杠)匹配,不做前缀匹配,所以 `/games/game_<32hex>/` 不会被误当成 SPA。
|
||||
3. **运行时口径**:`dev.genarrative.world` 与 `genarrative.world` 的 `/games`、`/games/detail?id=1`、`/games/play?id=1`、`/games/mine`、`/games/publish` 实测都返回 200 + SPA 外壳(`<title>陶泥儿 Genarrative|美术编辑器与项目工作台</title>`)。
|
||||
|
||||
验证证据:`deploy/pingora/nginx-route-parity.matrix.json` 新增 `games_spa_fallback`(`samplePath=/games/detail`,期望 `static/web/spa_fallback`),`cargo test -p pingora-gateway matches_nginx_route_parity_matrix` → **1 passed**;**变异验证**:临时从 `MAIN_SPA_PATHS` 拿掉 `"/games/detail"` 后该用例立刻失败(`route parity static mode mismatch: games_spa_fallback /games/detail,left: Some("exact") right: Some("spa_fallback")`),`check:pingora-route-parity` 同时报出缺路由。网关全量单测 `cargo test -p pingora-gateway` → **38 passed**;`npm run check:production-ops` 通过;`cargo fmt --all -- --check` 通过。
|
||||
|
||||
**顺带发现并另立待办**:三份 Nginx 模板都有 `/games/game_<32hex>/…` → 发行网关的代理(清 Cookie),Pingora 没有对应分支,切流后会 404。见 [`【待办】Pingora网关缺发行网关路由-2026-09-29.md`](【待办】Pingora网关缺发行网关路由-2026-09-29.md)。
|
||||
|
||||
@@ -46,10 +46,12 @@
|
||||
| `【待办】引用输入区后续收口-2026-09-24.md` | CSS 类名改名一条已按现状关闭(接受不改名);剩「归一化撞名是否提示」与真机手感验收 | 产品决定 + 真机 |
|
||||
| `【待办】画布验收后续修复-2026-09-18.md` | 三批修复与三项画布后续需求已落地本地;动画生成失败的真实复现、多选卡顿的现场 Profiler、以及全部真机观感仍未取证 | 真实客户端 + 现场 |
|
||||
| `【目标编辑器适配】Unity与Unreal项目识别与导入-2026-09-11.md` | 明确「暂不实现」的新功能 | 排期决定 |
|
||||
| `【待办】主站SPA路由白名单与前端路由源不一致-2026-09-24.md` | `check:nginx-spa-routes` 自 2026-08-26 起红灯;2026-09-24 已补回 `/components`、`/design-system`(失败清单从 7 条降到 5 条),剩 5 条 `/games*`;CI 与 `npm run lint` 都不含该门禁 | 剩游戏部分:属冻结的游戏分发系列,等阶段 A 验收结论 |
|
||||
| `【待办】Pingora网关缺发行网关路由-2026-09-29.md`(新增) | 三份 Nginx 模板都有 `/games/game_<32hex>/…` → 发行网关(清 Cookie),Pingora 的 `classify_path` 没有对应分支,切流后会 404;两个路由 parity 门禁与矩阵都覆盖不到这一条。当前 Pingora 只在 `127.0.0.1:18081` shadow,无用户可见故障 | 归 Pingora 试点切流前的对齐清单 |
|
||||
|
||||
## 五、已收口口径(供复核)
|
||||
|
||||
- **已关闭(2026-09-29)**:`【待办】主站SPA路由白名单与前端路由源不一致-2026-09-24.md`。nginx 侧 5 条 `/games*` 随 `87e52860a` 补齐;这轮补上漏掉的 Pingora 侧(`MAIN_SPA_PATHS` 5→12 条 + 矩阵新增 `games_spa_fallback` 用例 + 文档表同步)。`check:nginx-spa-routes` OK(12 路由 / 3 模板)、`check:pingora-route-parity` OK(22 路由)、`cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 1 passed(变异验证:拿掉 `/games/detail` 立刻变红)、网关全量 38 passed;dev 与生产上 `/games`、`/games/detail`、`/games/play`、`/games/mine`、`/games/publish` 实测 200 + SPA 外壳。该文件已在正文写明关闭记录,闭项从第四节移到本条。
|
||||
|
||||
- 74 份计划里 63 份为 `implemented-awaiting-runtime-acceptance`(含用括号写清边界的那批,含 `- Status:` 与表格两种写法);其余 11 份全部落在本文件一、二、三节里——这三节另外还列了 10 份状态已是 `implemented-awaiting-runtime-acceptance`、但仍有条目未勾选或未验收的计划,所以三节合计 21 份,不要与本条那 11 份混算。
|
||||
- 未勾选复选框从 85 条降到 43 条(2026-09-24 复算口径:`- [ ]` 行首复选框,74 份计划合计):本轮把已有本地证据的条目逐条勾选,并在每份计划里写了本轮的勾选依据或复验记录——多数是「逐条勾选依据(2026-09-24)」章节,另有「本轮复验(2026-09-24)」「本轮复核(2026-09-24)」写法,`【里程碑】DirectProject聊天真相源收敛-2026-09-16.md` 则把自动化证据直接写在条目里(`【自动化:…】`);仍勾不动的条目都能在依据里读到具体原因。
|
||||
- 仍为红灯的门禁(2026-09-24 实跑,与计划条目无关但需知晓):`npm run check:nginx-spa-routes`、`npm run check:pingora-route-parity`(`/components`、`/design-system` 已于本日补回,剩 5 条 `/games*` 的 SPA allowlist;详见 `【待办】主站SPA路由白名单与前端路由源不一致-2026-09-24.md`);两者都不在 CI 与 `npm run lint` 的覆盖范围内。
|
||||
|
||||
@@ -534,9 +534,11 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `/v1/database/{db}/subscribe`、`/v1/identity*` | 转发到 SpacetimeDB,保留 WebSocket Upgrade 头。 |
|
||||
| `/__genarrative_pingora/healthz` | 仅在携带 `X-Genarrative-Pingora-Probe` 且匹配配置 token 时返回 shadow JSON,否则 404。 |
|
||||
| `/v1/*`、`/generated-*`、`/healthz*`、`/readyz*` | 返回 404,保持生产公网不暴露口径。 |
|
||||
| 主站 SPA allowlist | 只对 `/`、`/creation`、`/project`、`/profile` 与 `/editor/canvas` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 主站 SPA allowlist | 只对 `/`、`/components`、`/creation`、`/design-system`、`/editor/canvas`、`/games`、`/games/detail`、`/games/mine`、`/games/play`、`/games/publish`、`/profile`、`/project` 失败回退 `/index.html`(集合与前端路由源、Nginx 三份模板逐条一致,由 `npm run check:pingora-route-parity` 与 `cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 比对);匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。`/games/game_<32 位十六进制 id>/…` 是发行网关路由,不在 SPA allowlist 内。 |
|
||||
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 不进入 SPA fallback。 |
|
||||
|
||||
SPA allowlist 里属于游戏分发入口的深链(游戏目录 / 详情 / 游玩 / 我的 / 发布深链:`/games`、`/games/detail`、`/games/play`、`/games/mine`、`/games/publish`)与 Nginx 三份模板同口径;Pingora 侧由路由对照矩阵的 `games_spa_fallback` 用例与 `cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 逐条断言。发行网关路径 `/games/game_<32 位十六进制 id>/…` 不走 SPA,见下一节的对照说明。
|
||||
|
||||
维护模式下,公网 API-like 路由返回 JSON `503`;公网 Web 静态路由先读取 `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_PAGE_FILE` 指向的 release 外运行态公告,缺失时回退 `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT/maintenance.html`,两者都不存在时返回纯文本 `503`。版本化默认页不得包含日期或具体时段,临时公告由 `maintenance-on.sh --page-file` 安装并在 `maintenance-off.sh` 时清理。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
|
||||
代理失败时,API / SpacetimeDB 等代理路由返回统一 JSON 网关错误;本地静态路由仍保持对应 HTTP 错误状态。
|
||||
静态 `Range` 只支持单段 bytes range;多段 range 暂按完整文件返回,避免在正式替换前引入 multipart 响应面。`If-None-Match` / `If-Modified-Since` 优先于 `Range` 判定,命中时仍返回 `304`;`If-Range` 日期匹配时继续返回 `206`,日期旧于文件或弱 ETag 校验器时回完整 `200`;`206` / `304` / `416` 不做 gzip 压缩,避免 `Content-Range` 语义被响应体改写破坏。Gateway smoke 会用固定 `X-Request-Id` 对账静态 `304`、`405`、`206`、`416` 的 Pingora access log 行,确认本地响应状态也进入正式切换证据链。
|
||||
|
||||
@@ -57,7 +57,24 @@ const SHADOW_PROBE_HEADER: &str = "x-genarrative-pingora-probe";
|
||||
const PAYLOAD_TOO_LARGE_CONTEXT: &str = "genarrative_payload_too_large";
|
||||
const PROTECTION_STATE_TTL: Duration = Duration::from_secs(600);
|
||||
const PROTECTION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
|
||||
const MAIN_SPA_PATHS: &[&str] = &["/", "/creation", "/editor/canvas", "/profile", "/project"];
|
||||
// 必须与前端路由源(`src/routing/activeAppPageRoutes.ts` 的 `STAGE_ROUTE_ENTRIES` 与
|
||||
// `src/routing/activeAppRoutes.tsx`)以及 nginx 三份模板的 SPA allowlist 逐条一致:
|
||||
// 门禁 `npm run check:pingora-route-parity` 会逐条比对,漏一条就等于把该深链在
|
||||
// Pingora 网关下打成 404(`/games/game_<id>/` 是发行网关路由,不在这个 allowlist 里)。
|
||||
const MAIN_SPA_PATHS: &[&str] = &[
|
||||
"/",
|
||||
"/components",
|
||||
"/creation",
|
||||
"/design-system",
|
||||
"/editor/canvas",
|
||||
"/games",
|
||||
"/games/detail",
|
||||
"/games/mine",
|
||||
"/games/play",
|
||||
"/games/publish",
|
||||
"/profile",
|
||||
"/project",
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct GatewayConfig {
|
||||
|
||||
Reference in New Issue
Block a user