这个故障是这样的。某个集成向你的接口发送 POST。接口是好的,路由也在,手工测一遍全都正常。可日志里出现的却是对一条只接受 POST 的路由发起的 GET,返回 404 或 405。

原因几乎总是同一个:客户端和接口之间横着一条 301 或 302 跳转。而这两个状态码并不要求客户端保留请求方法

十秒就能验证:

curl -sS -o /dev/null -L -d 'test=1' \
  -w '跳转后的方法:%{method}\n' \
  'https://httpbingo.org/redirect-to?url=/anything&status_code=301'

输出:跳转后的方法:GET。请求体也一并丢失。

把同一条命令里的 301 换成 308,得到的就是 POST。差别只在状态码。

五个状态码,两个属性

跳转在两个彼此独立的维度上有区别:是永久还是临时,以及请求方法是否保留。

状态码 名称 持久性 保留方法
301 Moved Permanently 永久
302 Found 临时
303 See Other 否,始终为 GET
307 Temporary Redirect 临时
308 Permanent Redirect 永久

注意其中的对称性:307 是带方法保证的 302,308 是带方法保证的 301。

这种混乱从何而来

从规范上讲,301 和 302 从来没有要求过更换方法。但早期的浏览器和客户端恰恰是这么做的,等到这个偏差被注意到时,整个网络早已按这种方式运转。

于是这种行为被写进规范,作为「允许」:现行的 HTTP 语义规范 RFC 9110 允许客户端在 301 和 302 时把 POST 换成 GET。是允许,而非要求——也就是说行为交由客户端自行决定,因此不能依赖。

为了给出一个明确无歧义的选项,才引入了 307 和 308。对它们而言更换方法是被禁止的:客户端必须以相同的方法和相同的请求体重发请求。308 由独立的 RFC 7538 定义,MDN 文档中也有参考说明。

实际结论:只有在请求确定是 GET 的地方,301 和 302 才是安全的。 凡是可能收到 POST 的地方,都需要 307 或 308。

第二个陷阱:永久跳转会被缓存

这一点比方法问题更容易被忘记。

301 和 308 声明的是永久迁移,浏览器有权将其缓存很长时间——有时要到用户清空缓存为止。此后服务器根本不会再被询问。

由此得出一条规则:错误的 301 几乎无法回滚。 你会去改配置,但已经拿到旧响应的访客仍会继续被跳转。302 和 307 没有这个问题,它们按定义就是临时的。

实用建议:在迁移方案尚未定型之前,使用临时状态码。日后把 302 提升为 301 很容易,反过来则不然。

该怎么选

页面永久迁移,且只接受 GET → 301。典型场景:旧文章网址、网址结构调整。

同上,但接口接受 POST → 308。API、Webhook、表单都属于这一类。

目标地址临时变更,只有 GET → 302。

临时变更,且方法必须保留 → 307。

POST 处理完之后把用户送到结果页 → 303。这就是 POST/Redirect/GET 模式:它有意把请求变成 GET,好让刷新页面不会重复提交表单。这里更换方法是目的,不是缺陷。

实践中它在哪里出问题

API 前面的 http→https 跳转。 最常见也最难受的情形。客户端配置的是 http://,服务器返回 301 指向 https://,方法随之丢失。症状是:日志里出现对 POST 路由的 GET。把 301 换成 308 即可解决。

主机名规范化。 www 到非 www(或反之)的跳转通常配成 301。对页面没问题,但如果同一主机上还住着 API,问题完全一样。

表单提交。 表单被提交到一个会跳转的地址。用户看到的是「什么都没发生」,因为数据根本没到达处理程序。

Webhook。 外部服务向你当初提供的地址发送 POST。如果那个地址经由 301 迁移了,Webhook 会悄无声息地停止工作,而发送方的日志里显示的是对跳转的成功响应。

这四种情形有共同特征:接口本身完好,但请求没有以原本的形态到达。于是排查方向就跑偏了——大家去处理程序里找 bug,而问题在 Web 服务器配置里。

短链服务为什么用 302

短链接几乎总是返回 302,这是有意的选择,而不是疏忽。

原因在于持久性,而不在方法:短链接的目标地址可能变更。用 301 就等于允许浏览器把这次跳转永久缓存,而在更换目标之后,一部分访客仍会落到旧地址上。这里方法无关紧要——短链接是用 GET 请求访问的。

机制本身另有说明,见短网服务是怎么工作的

状态码的选择不会改变什么

两个流传很广的误解。

链接权重。 在 301 和 302 之间选择并不影响权重传递——Google 早在 2016 年就已确认。完整讨论见短链接会传递链接权重吗

规范网址的选定。 跳转和 rel="canonical" 解决的是不同问题:跳转让一个地址退出流通,canonical 让两个地址都可访问并指明主地址。何时用哪个,见 canonical 完整指南

简短清单

  • 在接受 POST 的路由上使用 307 或 308,而不是 302 和 301。
  • 单独检查 http→https 跳转:它常由一条通用规则设定,而 API 会被遗漏。
  • 方案尚未定型时使用临时状态码。301 和 308 会被缓存,几乎不可逆。
  • 表单处理完之后用 303——这是正确行为,不是错误。
  • 把跳转链条压缩到一跳:每多一跳都同时增加延迟和风险。