摘要

  • 1996 年 5 月的 RFC 1945 称 302 为“Moved Temporarily”,POST 收到 302 后要经用户确认才能自动跳转。1997 年 1 月的 RFC 2068 继续设限,记下部分客户端仍把 POST 变成 GET,同时定义了用 GET 读取另一资源的 303 “See Other”。
  • 1999 年 6 月的 RFC 2616 把 302 改名“Found”,并写明当时大多数客户端把 302 当作 303:无论原请求方法如何,都对 Location 地址执行 GET。它用 307 “Temporary Redirect”明确要求保留方法;303 在 1997 年已经存在。
  • 2022 年 6 月的 RFC 9110 因历史原因仍允许客户端在 302 后把 POST 改成 GET;自动跟随 307 时则不得改变方法。差别决定表单正文会不会被送到新地址。

临时搬家,到底搬什么

1996 年 5 月的 HTTP/1.0 原始规范 RFC 1945把 302 叫作“Moved Temporarily”。资源暂时在另一个 URL,服务器用 Location 指出新地方;因为搬迁可能改变,客户以后仍应从原地址开始。对于 GET,故事像路口的指示牌:这儿找不到文档,去那里取。

POST 让事情复杂起来。用户可能填写了表单、提交了订单,或要求服务器执行一次动作。浏览器收到临时地址时,要不要把那份请求和数据重新发过去?1996 年文本规定,POST 收到 302,若没有用户确认,客户端不能自动跳转。问题不只是地址变了:把同一份正文交给另一个目标,可能改变用户当初同意提交时的条件。

一次重定向因此有两个轴。Location 回答下一次请求发往哪里,方法与正文决定下一次请求做什么。保留 POST,表单正文可能跟着去新服务器;换成 GET,则只在新位置取一份资源,不重送原提交。把 302 理解为单纯“换个网址”,会漏掉请求内容的命运。

早期标准对自动发送设了刹车。用户按过提交按钮,并不等于同意数据在新地址再走一次。可是网站也想让一次表单处理结束后立刻显示结果页,浏览器收到 302 总得有下一步。这些运行中的需求和纸上的限制很快发生摩擦。

303 已把 GET 说清,客户端却拿 302 做

1997 年 1 月的 HTTP/1.1 原始规范 RFC 2068仍称 302 为“Moved Temporarily”,并要求 GET、HEAD 以外的请求在自动跳转前获得用户确认。同一小节的注释却承认:一些 HTTP/1.0 客户端收到 302 后,会把 POST 错改成 GET 再跟随。规范没有把差异藏到后来的历史书里,自己就记下了正在发生的偏离。

1997 年文本还定义了 303 “See Other”。服务器处理完 POST,可以指向另一份资源,要求客户端用 GET 去取。由 POST 启动的脚本要展示结果页,是文档列出的主要用途。新 URI 是另一个可读取的结果,不是最初资源换了个名字。对于服务器作者,303 把“动作已处理,接着看结果”写得明明白白。

浏览器若把 302 擅自转成 GET,也会得到类似的下一页;在原 POST 已经处理完时,可能正合人意。但如果服务器希望 POST 在临时目标继续执行,新端点收到的却是一次读取。一个旧号码被塞进两种操作故事。此时冲突不在网页长什么样,而在第二个请求究竟保留了什么。

1999 年的规范面对多数客户端

1999 年 6 月的修订规范 RFC 2616把 302 改名“Found”。更关键的是注释:1996 与 1997 年的文本不允许跳转时改请求方法,但当时大多数客户端把 302 当作 303,不管原方法是什么,都对 Location 执行 GET。“大多数”是规范对现实客户端的原文判断;它没有列出每款浏览器的版本表。

规范给服务器提供更明确的选择。303 继续代表到另一个资源做 GET,尤其适合 POST 动作完成后的结果页。它并非 1999 年才发明,1997 年 RFC 2068 已经写明。1999 年加入的 307 “Temporary Redirect”则让另一种意图独立成号:临时换地址,但自动跟随时保留原请求的方法。两个号码分别表达“取结果”和“继续这次操作”。

302 并未因此退场。RFC 2616 甚至说明,不少旧客户端不懂 303;若服务器必须与它们互通,可以继续用 302,因为大多数会像处理 303 那样处理它。读起来颇有讽刺意味:清晰的号码已经在标准里,装机量却把服务器推回含糊的旧号。运行的软件让兼容性变成真正的投票。

对处理表单的网站来说,分界很实际。提交成功后,GET 一页结果不再发送表单正文;若业务必须在临时目标接续 POST,方法与正文都要过去。客户端对 302 的历史习惯不同,服务器只写这个数字,就把第二步的部分决定权交给了客户端。

旧习惯写进 2022 年标准

2022 年 6 月发布的 RFC 9110《HTTP Semantics》仍承认这段历史。它的 302 小节说,基于历史原因,客户端可以把 POST 改成 GET 去请求新位置。这是许可,不是每个客户端必须执行的命令。若服务器不想发生转换,文档指向 307。自动跟随 307 时,客户端不得改变请求方法。

同一规范把 303 描述为间接回答:客户端可到新资源做读取,通常是 GET 或 HEAD,并把那份结果呈现为原请求的答复。历史说明还写到,早期客户端在保留 POST 与改成 GET 之间分裂,301、302 的常见实践最终收敛到后者。标准修订没有宣布旧客户端从此不存在,而是给已经广泛运行的行为一个明文位置。

保留 POST 也并非总是更安全或更先进。307 的效果是让原方法继续,表单正文因此可能送到 Location 指向的新端点。若操作本来就应在那儿继续,这正是需要的行为;若新端点属于另一方,服务器作者必须正视数据将去哪里。改成 GET 避免第二次传正文,却无法完成确实需要继续 POST 的操作。号码要服务交易本身,不是追赶新旧标签。

这几份标准没有列出各浏览器首次改法的具体版本。带日期的实现记录可以补齐那条年表。标准原文已足以给出核心次序:1996 年的限制,1997 年记录偏离,1999 年承认多数实践并增加 307,2022 年保留历史许可。

方法也是争议的载荷

人们常把 3xx 画成指向新页面的箭头。302 的故事提醒我们,POST 装着用户的决定和数据。把同一请求再发到新 URI,与在那儿做一次 GET 不是同一个动作。Location 管地址,状态码和客户端行为管穿过边界的究竟是什么。

浏览器先跑出了一个广泛使用的答案。协议作者无法只靠重申旧段落让它消失。他们记录了缺口,给保留方法的选择另立 307,后来让 302 容纳已成惯例的 POST-to-GET。规范要约束的是已经在网络上工作的软件;它可以说清下一次如何选,却不能收回昨天已经发出的请求。

今天读这三个数字,表单有三种命运:302 可能把 POST 带向 GET;303 要取另一份结果;307 在自动临时跳转时保留原方法。最旧的那个号码之所以暧昧,是因为客户端在标准赶到之前先给出了自己的答案。

来源