二级目录上线:补斜杠补出死循环,子路径静态资源全 404

补一条 301 让 /admin 能打开,跳转链却变成两跳明文;另一个路径与 301 打成了死循环;子路径应用页面能开但样式全丢。三件事都指向同一处:前缀与尾斜杠的匹配边界。

二级目录上线那天,用户反馈很集中:/admin 打不开,/gitea 能打开但页面没有样式。我先用浏览器点了一遍,/admin 跳到主站登录页,地址栏是干净的;/admin/ 带斜杠却正常。这种"差一个斜杠"的差异,一般不是应用的问题,是入口匹配的问题。

后面查下去,一条斜杠牵出三件事:补斜杠补出了两跳明文、补斜杠和另一个服务的 308 打成了死循环、子路径应用没剥前缀导致静态资源全 404。

一、先分辨 404 是谁返回的

/admin、/tenant、/demo、/mobile 四个地址返回的都是 404,页面标题是"页面不存在"。但这个 404 不是网关的,是主站应用自己的 404 页——响应体 16920 字节,跟主站模板一致;网关的 404 只有 136 字节的 JSON。

道理很简单:这几条路由只写了带尾斜杠的匹配,/admin 不带斜杠,谁都不匹配,最后被主站那条 Prefix / 兜底接走,交给主站应用,主站自然不认识这个路径。

for p in /admin /admin/ /tenant /tenant/; do
  printf '%-10s code=%s size=%s\n' "$p" \
    "$(curl -s -o /dev/null -w '%{http_code}' https://example.com$p)" \
    "$(curl -s https://example.com$p | wc -c)"
done

对比响应体大小就能定位是谁的 404,这一步比看状态码有用得多。

二、补斜杠为什么要写绝对地址

修法很直接:给这几条路径加一组精确匹配,命中后 301 跳到带斜杠的地址。第一版我写的是相对跳转:

# 第一版:相对 Location
if ($request_uri ~ ^/(admin|tenant|demo|mobile)$) {
    return 301 /$1/;
}

结果 /admin 的跳转链是两跳:先 301 到 http://example.com/admin/,再被 WAF 的 80→443 规则 301 到 https。原因是 TLS 在最外层终结,到了 ingress 这一层 $scheme 已经是 http,而 nginx 默认 absolute_redirect on,相对 Location 会被补成 http://host/...。用户看到的是"打开了,但地址栏先闪一下明文"。

改成显式拼接 scheme 和 host:

# 修正:绝对地址,一跳到位
if ($request_uri ~ ^/(admin|tenant|demo|mobile)$) {
    return 301 https://$host$request_uri/;
}

跳数从 2 降到 1。这类"看起来只是慢一点点"的问题,实际是多一次明文请求,在弱网下体感明显,也会让埋点里的落地页统计失真。

三、301 撞上 308 就是死循环

补斜杠不能一把梭。有两个路径绝对不能碰:/umami 和 /api。这两个后端的框架配置是 trailingSlash=false,收到 /umami/ 会 308 跳回 /umami;我们再给 /umami 补 301 到 /umami/,两边就互相踢,浏览器直接报重定向过多。

路径 后端对尾斜杠的行为 能否补 301
/admin /tenant /demo /mobile 静态目录,需要尾斜杠 可以
/prod-api 网关,根路径两种写法都 404,接口路径不需要斜杠 可以(只为统一入口)
/umami 框架 trailingSlash=false,/umami/ 会 308 回 /umami 不可以,会死循环
/api 同上 不可以

判断方法是先打一次带斜杠的地址,看返回 200 还是 308。308 就是在告诉你"我不接受尾斜杠",这时候补 301 只会制造循环。

四、子路径应用必须剥前缀

/gitea 是另一个问题。它的 ROOT_URL 我们改成了 https://example.com/gitea/,页面能打开,但样式全丢,控制台一片 404。

一开始我以为是静态资源路径没配对,后来才想明白:ROOT_URL 只负责"生成链接时带上 /gitea/",它不改变应用内部的路由表。Gitea 内部的路由仍然是 /user/login、/assets/css/index.css。请求进来是 /gitea/assets/...,原样转给后端,后端没有这个路由,404。

所以子路径必须剥前缀:

# 24-ingress-gitea.yaml(节选)
metadata:
  name: web-top-gitea
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /gitea/?(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: git-svc
                port:
                  number: 3000

有个坑要记住:rewrite-target 是 Ingress 级注解,不是 path 级。如果直接给已有的 git-svc 那个 Ingress 加,它自己的 Prefix / 也会被重写成 /,等于把根路径规则改坏。正确做法是同 host 另建一个 Ingress,各自管各自的注解。

第一版我用的是 Prefix /gitea 且不剥前缀,现象是 /gitea 302 到 /gitea/user/login,然后 404,响应体 11 字节。11 字节这个细节很有用——它不是我们的应用返回的,是 Gitea 的空 404。

五、验收要覆盖"带斜杠/不带斜杠/静态资源"

这类改动最容易在"主页面能开"这一步就宣布完成。我列了一张清单,逐条打:

check() {
  printf '%-42s %s\n' "$1" \
    "$(curl -s -o /dev/null -w 'code=%{http_code} hops=%{num_redirects} size=%{size_download}' "$1")"
}
check https://example.com/admin
check https://example.com/admin/
check https://example.com/gitea
check https://example.com/gitea/user/login
check https://example.com/gitea/assets/css/index.css
check https://example.com/umami

结果:/admin 一跳 200,/admin/ 直接 200,/gitea 与登录页 200,CSS 372851 字节 200,/umami 仍是 307 到看板(这是它本来的行为,不能动)。

这里有个容易误判的点:Gitea 的 git info/refs?service=git-upload-pack 返回 401,/gitea/api/v1/version 返回 403。这两个都是应用自身的鉴权响应,路径是通的,不要当成入口故障去改配置。

六、回退要能在一条命令内完成

这次改动新增了三个 Ingress、改了一个环境变量。回退清单写清楚:

kubectl -n app delete ingress \
  web-top-trailing-slash web-top-gitea gitea-nip-subpath
kubectl -n app set env sts/gitea \
  GITEA__server__ROOT_URL=http://gitea.$INGRESS_VIP.nip.io/

删 Ingress 是即时的,改回 ROOT_URL 会滚动重启一次(约 30 秒)。两个动作顺序无所谓,但都要做——只删 Ingress 不改 ROOT_URL,页面里的链接仍然带 /gitea/,用户点进去还是 404。

小结

  • 带斜杠和不带斜杠的差异,先看 404 的响应体大小,判断是谁返回的,比猜快得多。
  • 补斜杠要用绝对地址,最外层终结 TLS 时 $scheme 已经是 http,相对跳转会多一跳明文。
  • 补斜杠之前先确认后端接受尾斜杠:返回 308 的后端一律排除,否则 301 与 308 会打成死循环。
  • 子路径应用要剥前缀,ROOT_URL 只影响链接生成,不影响内部路由表。
  • rewrite-target 是 Ingress 级注解,会连累同一对象下的其他 path,宁可另建一个 Ingress。
  • 验收要覆盖带斜杠、不带斜杠、静态资源三类地址;应用自身的 401/403 不要误判成入口故障。
查看「中间件」全部文章 →

同分类文章

查看 0 评论 0

评论

正在加载评论…

留言提交后自动审核:正常内容立即显示,含敏感信息的会转人工复核。

二级目录上线:补斜杠补出死循环,子路径静态资源全 404 - 起航社区