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

307 是临时重定向,硬规矩是「方法和正文都不许变」。 它和 303 一起,把 302 的含糊劈成了两半。

📝 本节和前面几节的关系

上一节 303 是「换成 GET」,这一节 307 是「保持原方法」。 两个码正好互补,也是新代码唯一该用的两个 3XX。

一、为什么要专门造这个码

302 的语义缺口是「方法要不要变」没说清。303 解决了「我要它变」的场景,那「我要它别变」的场景呢? 典型的例子是 API 版本迁移:POST /order 迁到 POST /api/v2/order。 如果服务端回 302,某些客户端会把 POST 改成 GET,新接口收到一个 GET, 直接 405 或者更糟——当成查询处理了。 307 就是为这个场景写的:客户端必须用原方法、原正文,只是把目标地址换掉。

二、307 与 308

308 Permanent Redirect 是 307 的永久版。和 301/302 的关系一样:临时用 307,永久用 308。 这一对是 2014 年才正式进 RFC 的(RFC 7538),所以书里没有。 2013 年写书的时候,307 已经在标准里了(1999 年就有),但 308 还没有。 选择表很简单: | 想干什么 | 用哪个 | |---|---| | 临时跳,换成 GET | 302?→ 用 303 | | 临时跳,保持方法 | 307 | | 永久跳,换成 GET | 301 | | 永久跳,保持方法 | 308 |

三、实测:两次都是 POST

Demo 里第一跳 POST /order,服务端回 307 加 Location: /api/v2/order; 客户端按 307 的要求用 POST 重新发一次,正文也原样带过去。 服务端日志里两次都是 POST ...(正文 6 字节)——这才是 307 该有的样子。 换成 302 或 303,第二次就会变成 GET(正文 0 字节)。 注意 307 之后那个请求是完整重发,不是「续传」。 所以如果原请求很大(比如上传 100 MB),307 会让客户端重传一遍——这是它的固有成本。

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

服务端第一跳回 307,客户端按语义用 POST 加原正文再发一次,打印两次请求行与正文长度。

if first.startswith(u'POST') and not hd.get(u'x-check'):
    return (u'HTTP/1.1 307 Temporary Redirect',
            [(u'Location', u'/api/v2/order')], b'')
# 按 307 语义:方法不变、正文不变,只换目标
s.sendall(_req(u'POST', hd.get(u'location'), body=b'item=1'))

实跑输出:

--------------------------------------------------------------------
307 Temporary Redirect:搬家但方法不许变
--------------------------------------------------------------------
  第 1 次请求                POST /order HTTP/1.1(正文 6 字节)
  第 2 次请求                POST /api/v2/order HTTP/1.1(正文 6 字节)

  第一跳                     HTTP/1.1 307 Temporary Redirect
  第二跳                     HTTP/1.1 200 OK
  307 的规矩                 **方法与正文都不许变**,只是地址变了
  308 与 307 的关系          308 是「永久版 307」
  307 存在的理由就一句话:302 没规定方法变不变,
  于是「把 POST 请求转到新地址」这件事没有可用的码。
  今天做 API 版本迁移,一律用 307/308,不能用 302。

第 1 次请求 POST /order(正文 6 字节)、 第 2 次请求 POST /api/v2/order(正文 6 字节)——方法和正文都保住了。 307 的规矩 方法与正文都不许变,只是地址变了。 308 是「永久版 307」——这一句是本书之外必须补的,因为书里没有 308。

30 秒实验

  1. 把 307 换成 303,重跑,看第二次请求变成什么方法。
  2. 把 307 换成 302,重跑,看你的客户端会怎么处理(本例写死了 POST,不会变)。
  3. 想一想:307 之后重发大文件上传统,代价是什么?有没有替代方案?

常见误区

  1. 「307 会跳转成 GET」——正好相反,307 强制保持原方法。
  2. 「307 是 302 的别名」——是两个码,语义不同。
  3. 「308 就是 301」——308 保方法,301 不保证。
自测三问(点开对答案) 1. 307 对方法有什么规定? —— 必须用原方法、原正文重新请求,只允许换目标地址。 2. 307 和 308 的区别? —— 307 临时,308 永久。语义上都是「保持方法」。 3. 307 重发大请求的代价是什么? —— 整个正文要重传一遍,没有「续传」机制。

3XX 讲完了。下一节进 4XX:客户端自己犯了什么错。

相关笔记