← 全部文章

· 阅读约 6 分钟

如何追踪并修复 HTTP 重定向链

重定向之后又是重定向,既浪费时间,又可能导致表单失效,还会向搜索引擎发出相互矛盾的信号。下面介绍如何看清每一跳、正确理解每个状态码,并把重定向链整理干净。

301、302、303、307 和 308 到底有什么区别

重定向是带有 Location 头的 3xx 响应。这些状态码的区别在于两点:迁移是否为永久性的,以及客户端是否可以改变请求方法。具体定义见 RFC 9110。

  • 301 Moved Permanently:永久。出于历史原因,客户端在跟随重定向时可以把 POST 改为 GET,而浏览器确实会这样做。
  • 302 Found:临时,同样存在把 POST 改为 GET 的历史例外。
  • 303 See Other:要求客户端用 GET(或 HEAD)获取目标资源。通常用于表单提交之后显示结果页面。
  • 307 Temporary Redirect:临时,且请求方法和请求体不得改变。
  • 308 Permanent Redirect:永久,且请求方法和请求体不得改变。

301 和 308 默认可缓存,因此浏览器可能直接复用它们而不再询问服务器;302 和 307 只有在响应带有明确的缓存头时才会被缓存。

用 curl 追踪重定向链

curl -sIL 会跟随每一次重定向,并输出每个响应的头部:

curl -sIL http://example.com/ | grep -iE '^(HTTP|location)'

两跳重定向链的典型输出如下:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/
HTTP/2 301
location: https://www.example.com/
HTTP/2 200

-I 发送的是 HEAD 请求,而有些服务器对 HEAD 和 GET 的响应不同。下面第一条命令用 GET 追踪并只输出头部;第二条输出一行摘要,包含跳转次数和最终 URL:

curl -sL -o /dev/null -D - https://example.com/
curl -sL -o /dev/null -w '%{num_redirects} hops, final: %{http_code} %{url_effective}\n' http://example.com/

每个入口都要测试,因为它们可能走不同的路径:

for u in http://example.com/ http://www.example.com/ https://example.com/ https://www.example.com/; do
  echo "== $u"; curl -sIL --max-redirs 10 "$u" | grep -iE '^(HTTP|location)'
done

curl 只跟随 Location 头。它不执行 JavaScript 或 meta refresh,默认情况下也不会在多次运行之间保留 HSTS 或重定向缓存,因此很适合作为基准,与浏览器中看到的结果进行对比。

在浏览器开发者工具中追踪

在 Network(网络)面板中启用 Preserve log(Chrome、Edge)或 Persist Logs(Firefox),这样页面跳转时列表不会被清空;再勾选 Disable cache,然后加载起始 URL。筛选文档类请求后,每一跳都会显示为一行,并带有状态码和 Location 响应头。

留意那些根本没有到达服务器的条目。Chrome 会把从缓存中复用的重定向标记为“from disk cache”,并把 HSTS 升级显示为 307 Internal Redirect,同时带有 Non-Authoritative-Reason: HSTS 头。这两者都是浏览器自己生成的,而不是你的服务器返回的。

常见问题

  • 重定向循环:A 重定向到 B,B 又重定向回 A,直到浏览器报出 ERR_TOO_MANY_REDIRECTS 之类的错误。常见原因是 CDN 或负载均衡器终止了 HTTPS,再通过 HTTP 访问源站,而源站不知道原始协议(例如因为它忽略了 X-Forwarded-Proto),于是不断重定向到 HTTPS。
  • 跳转次数过多:多年来不断添加的规则层层叠加,例如 http://example.com/page → https://example.com/page → https://www.example.com/page → https://www.example.com/page/。每多一跳就多一次往返。应让每条规则都直接指向最终 URL。
  • http → https → www 链:通常值得合并为一跳,但有一个例外。如果你希望裸域也启用 HSTS,或计划将其提交到 HSTS 预加载列表,http://example.com 应先重定向到同一主机上的 https://example.com,这样浏览器才能在那里收到 HSTS 头。
  • 规范信号不一致:重定向把用户带到 www,而 rel="canonical"、内部链接或站点地图仍在使用裸域、HTTP 或另一种末尾斜杠形式。
  • 301、302 或 303 导致方法改变:发往旧端点的 POST 在重定向后变成 GET,请求体被丢弃,表单或 API 调用随之失败。需要保留请求方法时,请使用 307 或 308。
  • 缓存的重定向与 HSTS:浏览器一旦收到 Strict-Transport-Security 头,就会在 max-age 到期之前自行把 http:// 改写为 https://,不发出任何网络请求。缓存的 301 也会以类似方式被复用。修复服务器并不会改变该浏览器中看到的结果,因此请用 curl 或全新的浏览器配置文件进行测试。在 Chrome 中,可以在 chrome://net-internals/#hsts 删除动态获得的 HSTS 条目,但预加载的条目无法删除。
  • meta refresh 和 JavaScript 重定向:服务器返回 200,随后页面通过 <meta http-equiv="refresh" content="0; url=/new"> 或 window.location 跳走。基于响应头的工具看不到这类重定向。可以用 curl -s https://example.com/ | grep -iE 'http-equiv|location\.(href|replace)' 查找它们,并尽可能改为服务器端重定向。

对 SEO 的影响

  • 把重定向链控制在一跳以内。每多一跳都会增加用户和爬虫的延迟,而且爬虫在跟随一定次数的跳转后就会停止。
  • 永久迁移使用永久重定向(301 或 308)。临时状态码会告诉搜索引擎原 URL 可能还会恢复。
  • 让重定向目标成为规范 URL:它应返回 200,带有指向自身的 rel="canonical",并且是内部链接、站点地图和 hreflang 标注中使用的 URL。
  • 把旧 URL 重定向到最接近的对应页面,而不是全部指向首页;迁移完成后也要长期保留这些重定向。
  • 优先使用服务器端重定向。meta refresh 和 JavaScript 重定向只有在页面被获取(对于 JavaScript 还需渲染)之后才会生效,而且并非所有爬虫都能处理。

快速检查清单

  1. 追踪全部四种变体:http 与 https,裸域与 www。
  2. 每种变体都在一跳内到达最终 URL(如果有意保留 HSTS 那一步,则为两跳)。
  3. 永久迁移使用 301 或 308;接收 POST 的端点使用 307 或 308。
  4. 最终 URL 返回 200,且规范标签指向自身。
  5. 内部链接、站点地图和规范标签都使用最终 URL。
  6. CDN 或代理之后不存在循环:用 curl 测试,而不只是用浏览器。
  7. 页面正文中没有 meta refresh 或 JavaScript 重定向。
  8. 在全新的浏览器配置文件中重新测试,排除缓存的重定向和 HSTS 的影响。

用 WebTraceCheck 检查

如果不想安装任何工具就检查重定向链,可以在 WebTraceCheck 上发起网站检查,无需注册账号。该审计会检查 HTTP 响应、重定向、DNS 和技术 SEO,列出每条观察结果,并注明其来源和测量局限。如果需要查看真实浏览器的行为,可以使用 GitHub 登录并打开一个临时云浏览器,自行选择浏览器引擎、地区和设备模式;它独立于你日常使用、已积累了缓存重定向和 HSTS 条目的浏览器。