📘 第四章 返回结果的HTTP状态码
📄 一句话概括

302 是临时重定向,本意是「暂时在别处」。 但它没规定「方法要不要变」,历史上因此出过很多乱子。

📝 本节和前面几节的关系

上一节 301 永久,这一节 302 临时。 两者的差别本该只是缓存,实际却牵扯到「POST 会不会被悄悄改成 GET」。

一、302 的历史包袱

HTTP/1.0 里这个码叫 302 Moved Temporarily,标准只说「去 Location」,没说用什么方法去。 于是各家实现自己决定:多数浏览器把 POST 改成 GET,有的保持 POST 重发。 同一段代码在不同客户端行为不同,这在当年是真实的兼容性地狱。 HTTP/1.1 把这个码改名成 Found(名字更含糊了),并且加了一句「除非请求方法是 HEAD, 客户端可以改 GET」——仍然是「可以」,不是「必须」。歧义没解决。

二、补丁:303 与 307

正因为 302 说不清,后来才补了两个码把语义拆开: | 码 | 方法怎么办 | 典型用途 | |---|---|---| | 302 | 含糊(多数改 GET) | 兼容老系统 | | 303 | 强制改 GET | POST 之后看结果 | | 307 | 强制保持原方法 | API 临时迁移 | 所以「302 用在哪里」的正确答案是:新代码不该用它。要换方法就用 303,要保方法就用 307。 302 只剩一个用途:跟只认 302 的老系统打交道。这个现实在 2026 年依然存在。

三、一个真会咬人的场景

表单提交后回 302 指向结果页。浏览器把 POST 改成 GET 去请求,用户看到结果,一切正常。 但如果某个客户端保持了 POST,它会把整个表单数据重新提交一次——重复下单。 这就是为什么支付流程绝对不能用 302。 Demo 里模拟的是主流浏览器的行为:第一跳 POST,第二跳 GET,请求体丢了。

四、动手 Demo:302 之后方法变了没有

服务端收到 POST 回 302,客户端按主流浏览器行为改用 GET 跟过去, 把服务端两次收到的请求行打出来对比。

if path == u'/login':
    return (u'HTTP/1.1 302 Found', [(u'Location', u'/home')], b'')
# 模拟浏览器:302 之后改成 GET
s.sendall(_req(u'GET', loc))

实跑输出:

--------------------------------------------------------------------
302 Found:临时搬家,方法可能会变
--------------------------------------------------------------------
  服务端第 1 次收到          POST /login HTTP/1.1
  服务端第 2 次收到          GET /home HTTP/1.1

  第一跳                     HTTP/1.1 302 Found
  第二跳用了                 GET(模拟主流浏览器的历史行为)
  302 的语义                 临时。不该缓存,但「方法要不要变」标准没说清
  结果                       POST 被悄悄变成 GET,请求体丢了
  这就是 302 的历史包袱:早期实现各自为政,
  有的把 POST 改 GET,有的原样重发 POST。所以后来补了 303 和 307。
  今天写代码,跳转一律用 303(要换 GET)或 307(要保持方法),
  302 只在兼容老系统时出现。

服务端第 1 次收到 POST /login、第 2 次收到 GET /home——方法在跳转过程中被悄悄换掉了。 结果 POST 被悄悄变成 GET,请求体丢了——如果那个请求体是订单,你就丢了一笔订单; 如果客户端保持 POST,你就下了两次单。两种都糟。 所以本节真正的结论不是「302 是什么」,而是「302 靠不住,别用」。

30 秒实验

  1. 把服务端改成 307,重跑,看第二次收到的是 POST 还是 GET。
  2. 把服务端改成 303,重跑,看结果(应该也是 GET)。
  3. 想一想:为什么 HTTP/1.1 把这个码的名字从「临时移动」改成「Found」?

常见误区

  1. 「302 就是临时版的 301」——除了缓存,方法处理也不一样。
  2. 「302 之后一定是 GET」——标准写的是「可以」,不是「必须」。
  3. 「302 和 303 一样」——303 才是强制 GET。
自测三问(点开对答案) 1. 302 的语义缺口是什么? —— 没规定跳转时用不用原方法,导致各家实现不一致。 2. 要「POST 之后跳去看结果」该用哪个码? —— 303 See Other。 3. 支付流程为什么不能用 302? —— 如果客户端保持 POST,会把支付请求重新提交一次,造成重复扣款。

302 说不清方法,303 就是来把这件事说清的。

相关笔记