系统设计 / 路由设计 · 稳定入口与可变目标 / HTTP 重定向的五个状态码 待审核 4 / 23

HTTP 重定向的五个状态码

路由匹配到一条请求后,handler 不一定要返回内容——它也可以回一个 3xx 状态码 + 一个 Location 响应头,意思是「要找的东西不在这个地址,去那边」。客户端读到后会自动再发一次请求去 Location。这就是重定向:服务端只改 Location,就能把同一个入口指向任意新目标。

五个重定向码看着像近义词,实际只差两个正交维度:永久还是临时(决定能不能被浏览器缓存)、跳过去时方法保不保持(POST 保持 POST,还是被悄悄改成 GET)。

图 0-1 · 客户端逐跳 follow 的过程:方法是否被改写、跳转链如何收敛到 200。可选状态码与原始方法观察差异。

永久的那两个(301 与 308)意思是「这个地址已停用,以后都走新的」,浏览器会缓存这条跳转,下次直接去新址、连旧址都不请求,因此配错很难收回。临时的三个(302、303、307)意思是「暂时去那边,旧址还作数」,默认不缓存。

方法这一维,307 与 308 严格保持方法与 body;301 与 302 则不确定——规范原本不打算允许改方法,但现存的一些 user agent 确实会把非 GET 改成 GET,307 与 308 正是为消除这个歧义而添加的 [1]。303 则是明确要求改:GET 不变,其余方法一律改成 GET 且丢弃 body。

两个维度四种组合,加上 303 这个「强制改 GET」的特例。选型时先问「是不是永久」,再问「方法能不能被改」。
状态码 永久? 方法 典型用途
301 Moved Permanently 永久,可缓存 可能被改 GET 永久搬家:旧 URL 不再用,把权重与书签迁到新址
302 Found 临时 可能被改 GET 临时跳转,最常用也最常被误用(多数场景想要的是 307 或 303)
303 See Other 临时 强制改 GET POST 之后跳到一个 GET 结果页,即 PRG 模式防重复提交
307 Temporary Redirect 临时 原样保持 临时跳转且严格保持方法与 body
308 Permanent Redirect 永久,可缓存 原样保持 永久跳转且严格保持方法与 body,API 端点搬迁的首选

想「临时跳又别改方法」用 307,想「永久跳又别改方法」用 308,POST 提交后跳到结果页防重复提交用 303。

1 · 把重定向当可配置的跳转链接

对外只暴露一个稳定端点 /api/users,而后端实现在不断升级(v1v2 再到 v3)。做法是让 /api/users 回一个 308,Location 指向当前版本。需求一变,服务端改一行 Location 配置即可把所有调用方导到新版本,而调用方代码里那个 /api/users 不用改。

为什么是 308 而不是 301:API 调用常是 POSTPUT,308 严格保持方法与 body,POST /api/users 会原样变成 POST /api/v2/users;若用 301,POST 可能被某些客户端改成 GET,请求体丢失、语义全错。

警示 · 重定向不是免费的,每一跳都是一次完整往返。链路越长越慢,还可能绕成循环。服务器有时能自己发现循环并回 500,但跨多台服务器的循环各方都看不到全貌,只能由浏览器兜底检测并报错;Chrome 与 Firefox 的跳数上限都是 20。能在服务端内部 rewrite 解决的就别让客户端多跑一趟外部 redirect。另外 301 与 308 一旦被缓存就很难纠正,上线前务必想清楚这真的是永久吗。

3xx 的 handler 同样要先由匹配引擎把请求 path 命中到它才有机会执行;分享地址统一入口命中后用的正是 302,因为分享目标常变。

2 · 参考文献

  1. MDN Web Docs. Redirections in HTTP(五个 3xx 码的方法处理与缓存语义、重定向循环). Mozilla.(核对于 2026-08)