From 5e289b9eb6c1c6200cd74159913c3308f66347ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Mon, 19 Oct 2026 15:27:37 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20spec=20=E7=BB=88=E5=AE=A1=E6=94=B6?= =?UTF-8?q?=E5=B0=BE=EF=BC=88=C2=A71=20=E6=9C=AB=E6=AE=B5=E4=B8=80?= =?UTF-8?q?=E8=AF=8D/=C2=A73.1=20raw=5Fpath=20=E7=9B=B4=E5=88=A4=E4=B8=BA?= =?UTF-8?q?=E5=87=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .../specs/2026-10-19-shared-domain-routing-design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/superpowers/specs/2026-10-19-shared-domain-routing-design.md b/docs/superpowers/specs/2026-10-19-shared-domain-routing-design.md index 09b8c0d..51b9f93 100644 --- a/docs/superpowers/specs/2026-10-19-shared-domain-routing-design.md +++ b/docs/superpowers/specs/2026-10-19-shared-domain-routing-design.md @@ -10,7 +10,7 @@ 现状租户按域名(Host)解析,一租户至少一个域名——给大量网站挂客服时 DNS/证书/反代条目线性增长,不适合运营。目标:**一个共享域名承载任意多租户**,租户标识在 URL 路径中;Host 模式原样保留,两种共存。 上线收口项(随本 spec 一并实施): -1. **登录限流 XFF**:反代形态下 `client.host` 恒为 127.0.0.1,登录限流键退化为纯用户名,多 IP 爆破同一账号不受 IP 维度限制——解析 XFF 首段入键; +1. **登录限流 XFF**:反代形态下 `client.host` 恒为 127.0.0.1,登录限流键退化为纯用户名,多 IP 爆破同一账号不受 IP 维度限制——解析 XFF 末段入键; 2. **limiter 清扫**(日志/内存上限铁律的进程内面):登录限流器与聊天限流桶的过期键永不回收,周期清扫。 ## 2. 方案总览 @@ -30,7 +30,7 @@ Host 模式(不动):chat.xxx.com → registry.resolve_site(host) 优先级:**平台豁免 → 路径前缀解析 → Host 解析**。 -1. **slug 形态校验先行**:在**解码后的 `scope["path"]`** 上校验前缀段须 `^[a-z0-9-]+$`(与租户创建 `SLUG_RE` 一致,tenant_routes.py:65 同款正则常量提取共用);不匹配(含 `..`、大小写不符、含编码百分号的原始形态)直接走 Host 解析(共享域名下即 404),**不做任何剥离**——注意 `/t/%61uto/` 解码后虽成合法 slug,但原始形态含编码即不匹配校验(校验看 path 的原始段需由未编码字符构成:实现取 raw_path 对应段做 ASCII 直判,或等价地在 path 段上拒绝含 `%` 的输入——`%` 不在合法 slug 字符集内,解码后 slug 不可能含 `%`,故「解码后含 %」必为编码形态,拒绝)。 +1. **slug 形态校验先行**:校验在 **`raw_path` 对应段**上做 ASCII 直判(段须为未编码的 `^[a-z0-9-]+$`,与租户创建 `SLUG_RE` 一致,tenant_routes.py:65 同款正则常量提取共用)——`/t/%61uto/` 解码后虽是合法 slug,但原始段含编码即拒(**raw_path 直判为准**;解码后含 `%` 的拒绝路线不充分——`%61` 解码为字母后 `%` 不复存在,故不可依赖解码形态判编码)。不匹配(含 `..`、大小写不符、任何编码形态)直接走 Host 解析(共享域名下即 404),**不做任何剥离**。 2. **路径改写**:匹配 `/t//` 或精确 `/t/`(后者无尾斜杠 → 不剥离,走 Host 解析 → 共享域名 404,语义记录在案);`scope["path"]` 剥前缀;`scope["raw_path"]` **按剥离后的解码 path 重新编码重算**(不按长度截断——编码形态下解码/原始长度不一致);http 与 websocket 两类 scope 同构处理。 3. **orig_path 留痕**:原始 path 存 `scope["state"]["orig_path"]`(排障/日志对账用;uvicorn 访问日志显示剥离后路径为已知性状)。 4. **解析与错误语义**:slug 经 registry 公开方法 `resolve_by_slug`(见 §5;中间件不触私有 `_by_slug`)——未知 slug → http 404 / ws close(1008);停用 → 503 暂停页 / ws close(1013)(与 resolve_site 同款语义);站点 = 租户**首个站点**为默认(`?site=` 覆盖链路既有),`state["site"]` 同样写入。