HTTP 重定向 · 301/302/303/307/308 到底差在哪
路由匹配到一条请求后,handler 不一定要返回内容——它也可以回一个 3xx 状态码 + 一个 Location 响应头,意思是「你要的东西不在这里,去那边」。客户端读到后会自动再发一次请求去 Location。这就是重定向:服务端只改
Location,就能把同一个入口指向任意新目标。
五个重定向码看着像近义词,其实只差两个正交维度:永久还是临时(决定能不能被浏览器缓存)、跳过去时方法保不保持(POST 保持 POST,还是被悄悄改成 GET)。
两维度速记。 永久(301 / 308):「这个地址已停用,以后都走新的」——浏览器会缓存这条跳转,下次直接去新址、连旧址都不请求了(配错很难收回)。临时(302 / 303 / 307):「暂时去那边,旧址还作数」——默认不缓存。方法保持(307 /
308):POST 原样带着 body 跟过去。方法可能被改(301 / 302):历史上浏览器把非 GET 偷偷改成 GET——行为不确定,这正是 307/308 被发明出来的理由。强制改 GET(303):无论原方法,一律改 GET。
| 状态码 | 永久? | 方法 | 典型用途 |
|---|---|---|---|
| 301 Moved Permanently | 永久 · 可缓存 | 可能被改 GET | 永久搬家:旧 URL 不再用,把 SEO 权重 / 书签迁到新址 |
| 302 Found | 临时 | 可能被改 GET | 临时跳转:最常用也最常被误用(其实多数场景想要的是 307 或 303) |
| 303 See Other | 临时 | 强制改 GET | POST 之后跳到一个 GET 结果页——经典 PRG(Post/Redirect/Get)防重复提交 |
| 307 Temporary Redirect | 临时 | 原样保持 | 临时跳转,且严格保持方法与 body(POST 仍是 POST) |
| 308 Permanent Redirect | 永久 · 可缓存 | 原样保持 | 永久跳转,且严格保持方法与 body——API 端点搬迁 / 版本切换的首选 |
想「临时跳又别改方法」用 307;想「永久跳又别改方法」用 308;POST 提交后跳到结果页防重复提交用 303(PRG)。
1 · 把它当「可配置的跳转链接」· 版本切换
回到一个真实需求:对外只暴露一个稳定端点 /api/users,但后端实现在不断升级
。做法是让 /api/users 回一个 308,Location 指向当前版本。需求一变,服务端改一行 Location 配置即可把所有调用方导到新版本,而调用方代码里那个 /api/users 不用改。
为什么偏偏是 308 而不是 301?因为 API 调用常是 POST / PUT:308 严格保持方法与 body,POST /api/users 会原样变成 POST /api/v2/users;若用 301,POST 可能被浏览器 / 某些 client 改成 GET,请求体丢失、语义全错。上面把状态码切到 308、方法切到
POST、点版本预设,观察方法保持不变地跟到新版本。
重定向不是免费的。 每一跳都是一次完整的往返(RTT)。链路越长越慢,还可能绕成循环(),所以客户端会限制最大跳数(浏览器约 20 跳后报错)。能在服务端内部 rewrite(内部转发) 解决的,就别让客户端多跑一趟外部 redirect。另外:301/308 一旦被缓存就很难纠正,上线前务必想清楚「这真的是永久吗」。
2 · 相关页
- 本系列 · 分享地址统一入口——分享入口命中后,正是用一个
302(临时,因目标常变)把用户跳到当前活动目标。 - 本系列 · 路由匹配引擎——回
3xx的 handler,也是先由匹配引擎把请求 path 命中到它,才有机会做重定向。 - MDN · HTTP Redirections · developer.mozilla.org——五个 3xx 码的权威逐项说明、永久 vs 临时、方法保持的细节。
</content>