303 干脆规定死:用 GET 去 Location,不带原请求体。 它存在的唯一目的就是实现 PRG(Post/Redirect/Get)。
上一节说 302 没规定方法。 这一节看标准怎么补的——不是改 302,而是新造一个码,保证老客户端行为不变。
一、PRG 模式
POST 处理完不要直接返回结果页,而是回 303 指向结果页。
浏览器用 GET 去取那个页面,用户看到的是 GET 的结果。
这样刷新就变成了「重新 GET 结果页」,不会重发 POST。
这就是 PRG(Post/Redirect/Get),Web 表单的标配。
Demo 里订单接口就是这套:POST /order → 303 → GET /result/42。
服务端日志里两次请求方法清清楚楚。
二、为什么不能靠 302 实现
因为 302 不保证方法变成 GET。
如果某个客户端保持 POST 跟过去,/result/42 就会收到一个 POST——服务端要么不认(405),
要么把它当写操作处理(灾难)。
303 把这件事写死了:客户端必须用 GET。
所以服务端可以放心假设/result/42 只处理 GET,缓存也可以放心缓存它。
顺带一个细节:303 的响应正文通常为空,但可以带一段说明文字供老客户端显示。
三、303 与 307 是一对
这两个码是 1999 年一起补进 HTTP/1.1 的,正好把 302 的歧义劈成两半: 303 = 换成 GET(用于表单后的跳转);307 = 保持原方法(用于接口迁移)。 记住这一对,302 就没有存在的必要了。 这也是为什么现代框架(Django、Rails、Spring)的重定向默认行为都尽量靠近 303/307。
四、动手 Demo:订单提交:POST 进来,GET 出去
服务端收到 POST 就回 303,客户端按 303 的要求用 GET 去 Location,把两次请求行和正文长度都打出来。
if first.startswith(u'POST'):
return (u'HTTP/1.1 303 See Other',
[(u'Location', u'/result/42')], b'')
# 303 要求:用 GET 去 Location
s.sendall(_req(u'GET', hd.get(u'location')))
实跑输出:
--------------------------------------------------------------------
303 See Other:POST 之后去看结果
--------------------------------------------------------------------
第 1 次请求 POST /order HTTP/1.1(正文 6 字节)
第 2 次请求 GET /result/42 HTTP/1.1(正文 0 字节)
303 强制要求 用 **GET** 去 Location,不带原请求体
这就是 PRG Post/Redirect/Get,防重复提交的标准套路
PRG 解决的是「刷新会不会重复下单」:
POST 处理完回 303,浏览器跳到结果页(GET),
用户刷新的是那个 GET 页面,不会重发 POST。
302 做不到这一点,因为它没规定换不换方法;303 明确规定了。
第 1 次请求 POST /order(正文 6 字节)、第 2 次请求 GET /result/42(正文 0 字节)——方法换了、
正文没了,这正是 303 要求的。
303 强制要求 用 GET 去 Location,不带原请求体。这一句是本节全部内容。
这就是 PRG Post/Redirect/Get,防重复提交的标准套路——记住这个名字,面试和排障都用得上。
30 秒实验
- 把 303 换成 302,重跑,看你的客户端(模拟浏览器)行为会不会变(本例不会,因为脚本写死了 GET)。
- 把 303 换成 307,看第二次请求会变成什么方法。
- 想一想:如果结果页需要登录,303 之后那个 GET 会带 Cookie 吗?(会)
常见误区
- 「303 是 302 的新名字」——是两个码,302 仍然存在,只是语义含糊。
- 「303 之后客户端可以选方法」——不行,必须 GET。
- 「PRG 只在表单场景有用」——任何「提交后展示结果」的流程都适用。
自测三问(点开对答案)
1. 303 对方法有什么硬性规定? —— 客户端必须用 GET 去 Location,不带原请求体。 2. PRG 解决什么问题? —— 用户刷新结果页时不会重新提交 POST,避免重复下单。 3. 303 和 307 分别对应什么场景? —— 303 换成 GET(表单后跳转);307 保持原方法(接口迁移)。跳转讲了一半。下一节看一个特殊的 3XX:它根本不跳,只是说「没变」。
- ☐ (本节完,下一节 4.3.4 304 Not Modified)