# HTTP的重定向
30 秒速记
- 重定向 = 服务器回
3xx+Location,浏览器自己再发一次请求,所以一共两个来回 - 永久还是临时:
301/308永久,302/307临时,永久的会被浏览器缓存、被搜索引擎改索引 - 方法变不变:
303强制改成GET;307/308保留原方法和请求体;301/302遇到POST浏览器通常会改成GET - 站内跳转写相对路径就行,跳站外必须写完整的
https://... - 每多一跳多一个来回,规则写乱了还会出现循环,浏览器会报「重定向次数过多」
重定向就是服务器回一个 3xx 状态码加 Location 头,告诉浏览器「去那个地址重新要」,浏览器会自动再发一次请求。 选状态码我只看两件事:这次搬家是永久还是临时,跳过去之后请求方法能不能变。永久用 301,临时用 302;接口里要保住 POST 和请求体就用 307 或 308;表单提交完跳结果页用 303。它和服务端内部转发不一样,浏览器地址栏会变,而且多一个网络来回,所以链路要尽量短,规则之间也别互相指。
面试官追问
追问 1活动页只想临时跳两周,开发写了 301,活动结束后改了配置用户还是被跳走,为什么?
301 会被浏览器缓存,没设 Cache-Control 的话可能一直记着,后面改服务端配置老用户也收不到。临时跳转一律用 302,已经发出去的 301 只能等缓存过期或者让用户清缓存。
追问 2网站换了新域名,旧域名上的搜索流量怎么尽量保住?
旧域名全站 301 到新域名的对应路径,/a/b 跳 /a/b,别全扔到首页,搜索引擎会把权重转过去。同时站内链接、sitemap、canonical 都换成新域名,减少一跳。
追问 3用户访问 /account 浏览器报「重定向次数过多」,怎么查?
curl -IL --max-redirs 10 https://xx.com/account 把每一跳的状态码和 Location 打出来,基本一眼就能看到 A → B → A。最常见的是 CDN 回源用 http,源站又强制跳 https,或者登录态判断在网关和应用两层不一致。
追问 4前端 axios 请求接口,后端登录过期返回 302 到登录页,前端为什么拦不到?
浏览器的 XHR 和 fetch 会自动跟随重定向,前端拿到的是登录页那个 200 的 HTML。接口场景应该返回 401,由前端拦截器自己 location.href = '/login',别用 302。
# 重定向的过程
30 秒速记
- 重定向 = 两次独立请求:第一次拿到
3xx+Location,浏览器再按Location发第二次 - 用户基本无感,地址栏会变成新地址,
DevTools勾上Preserve log才看得到中间那跳 Location是响应头,只有配合3xx(还有201)才有意义- 相对地址按当前请求的
URL解析,站内可以用;跳站外必须写全https:// ?dst=nginx.org会被解析成本站下的路径,变成https://本站/nginx.org
重定向的秘密全在响应头 Location 上:服务器回 301 或 302,带上新地址,浏览器看到就自动再请求一次。 所以它其实是两次完整的 HTTP 请求,第二次有自己的请求行和请求头,和服务端内部转发是两码事。Location 可以写相对地址,比如 /index.html,浏览器会拿当前地址补全;但跳到别的站必须写全,少了 https:// 就会被当成本站的一个路径。开发时 DevTools 记得勾 Preserve log,不然跳完中间那条请求就被清掉了。
301是“永久重定向”,302是“临时重定向”,浏览器收到这两个状态码就会跳转到新的URI。- 那么,它们是怎么做到的呢?难道仅仅用这两个代码就能够实现跳转页面吗
- “重定向”实际上发送了两次
HTTP请求,第一个请求返回了302,然后第二个请求就被重定向到了“/index.html”。但如果不用开发者工具的话,你是完全看不到这个跳转过程的,也就是说,重定向是“用户无感知”的
我们再来看看第一个请求返回的响应报文:

这里出现了一个新的头字段“Location: /index.html”,它就是 301/302 重定向跳转的秘密所在。
“Location”字段属于响应字段,必须出现在响应报文里。但只有配合 301/302 状态码才有意义,它标记了服务器要求重定向的 URI,这里就是要求浏览器跳转到“index.html”。
浏览器收到 301/302 报文,会检查响应头里有没有“Location”。如果有,就从字段值里提取出 URI,发出新的 HTTP 请求,相当于自动替我们点击了这个链接。
在“Location”里的 URI 既可以使用绝对 URI,也可以使用相对 URI。所谓“绝对 URI”,就是完整形式的 URI,包括 scheme、host:port、path 等。所谓“相对 URI”,就是省略了 scheme 和 host:port,只有 path 和 query 部分,是不完整的,但可以从请求上下文里计算得到
注意,在重定向时如果只是在站内跳转,你可以放心地使用相对 URI。但如果要跳转到站外,就必须用绝对 URI。
例如,如果想跳转到 Nginx 官网,就必须在“nginx.org”前把“http://”都写出来,否则浏览器会按照相对 URI 去理解,得到的就会是一个不存在的 URI“http://www.chrono.com/nginx.org”
http://www.chrono.com/18-1?dst=nginx.org # 错误
http://www.chrono.com/18-1?dst=http://nginx.org # 正确
面试官追问
追问 1重定向和服务端内部转发有什么区别?
重定向是浏览器发了两次请求,地址栏会变;内部转发是服务端自己把请求交给另一个处理器,浏览器只发了一次、地址栏不变,比如 Nginx 的 try_files 或者 proxy_pass。
追问 2res.writeHead(302, { Location: 'google.com' }),浏览器最后跳到了哪?
跳到 https://当前域名/google.com。没写协议就是相对地址,浏览器按当前 URL 补全。跳站外要写 https://google.com。
追问 3登录页支持 ?redirect= 参数登录后跳回原页面,有什么安全问题?
开放重定向。攻击者发一个 https://你的域名/login?redirect=https://钓鱼站,用户看域名是真的,登录完就被带走了。只允许相对路径,或者对目标做域名白名单校验,注意 //evil.com 这种也要拦住。
追问 4fetch 能拿到重定向之前的那个 302 吗?
浏览器里不能。redirect: 'manual' 只会给你一个 type 为 opaqueredirect 的响应,状态码是 0,看不到 Location。Node 18 自带的 fetch 没这个限制,能拿到真实的 302 和头。
追问 5POST 请求收到 302 后,第二次请求还是 POST 吗?
浏览器通常会改成 GET 并丢掉请求体,这是历史遗留行为。要保住方法和请求体就用 307 或 308。
# 重定向状态码
30 秒速记
301永久:浏览器会缓存,搜索引擎更新索引,旧地址的权重转到新地址302临时:原地址还有效,浏览器不记,下次照样访问旧地址303跳完必须用GET,经典场景是表单POST完跳结果页,防重复提交307= 不许改方法的302,308= 不许改方法的301- 易错点:
301/302收到POST时,浏览器实际会改成GET再请求,接口迁移要用307/308
状态码就按两个维度选:永久还是临时,跳过去请求方法能不能变。 永久搬家用 301,浏览器会缓存,搜索引擎会把旧地址的权重转到新地址;临时跳走用 302,原地址还算数。麻烦在 301 和 302 对 POST 的处理,规范本意是不改方法,但浏览器历史上都改成了 GET,于是补了 303、307、308:303 明说改成 GET,307 和 308 明说不许改。现在这三个码主流浏览器早就支持了,接口迁移放心用 307 或 308。
刚才我把重定向的过程基本讲完了,现在来说一下重定向用到的状态码。
最常见的重定向状态码就是 301 和 302,另外还有几个不太常见的,例如 303、307、308 等。它们最终的效果都差不多,让浏览器跳转到新的 URI,但语义上有一些细微的差别,使用的时候要特别注意。
301俗称“永久重定向”(Moved Permanently),意思是原 URI 已经“永久”性地不存在了,今后的所有请求都必须改用新的 URI。
浏览器看到 301,就知道原来的 URI“过时”了,就会做适当的优化。比如历史记录、更新书签,下次可能就会直接用新的 URI 访问,省去了再次跳转的成本。搜索引擎的爬虫看到 301,也会更新索引库,不再使用老的 URI。
302俗称“临时重定向”(“Moved Temporarily”),意思是原 URI 处于“临时维护”状态,新的 URI 是起“顶包”作用的“临时工”。
