PRG 是什么
表单提交后用重定向跳到结果页的经典模式:提交请求处理成功后返回 303,浏览器再对结果页发起一次读取请求(RFC 9110 §15.4.4)。用户此后刷新页面只会重复读取,而不会重复提交表单。
为什么用 303
303 的语义就是「去看看另一个地址」,它明确要求客户端把方法改成读取。用 302 在历史上依赖浏览器的宽容行为,部分客户端可能保留原方法,刷新后又提交一次。语义最清晰的做法只有 303。
基本信息
| 由来 | 终止浏览器「重新提交表单」弹窗的根本方案 |
|---|---|
| 实现 | 提交成功后返回 303,并指向结果页 |
| 规范 | RFC 9110 §15.4.4 |
| 替代方案 | 302 与 307 也能用,但语义不如 303 清晰 |
典型使用场景
- 后台管理系统的表单提交
- 下单、发布、审核等会产生副作用的操作
- 服务端渲染页面的常规流程
- 排查「刷新后重复下单」类缺陷
前后端拆分下的流程
前后端拆分下的流程
POST /submit → 303 Location: /result?id=42
GET /result?id=42 → 200 展示结果
(用户刷新 /result 只是读取,不会重复提交)状态码怎么选
| 状态码 | 方法是否保持 | 适用场景 |
|---|---|---|
| 303 | 改为读取 | 表单提交后跳转,语义最清晰 |
| 302 | 历史实现各行其是 | 兼容老客户端时使用,语义含糊 |
| 307 | 保持原方法 | 需要再次提交同一请求时 |
| 301 与 308 | 永久重定向 | 地址永久变更,与表单流程无关 |
接口型服务通常不用这个模式(客户端自己处理跳转),它主要面向浏览器表单与服务端渲染页面。
重定向后的结果页如果又做了非幂等副作用(例如再发一次通知),等于把重复问题换了个地方:刷新依旧会重复触发。
失败路径不适合重定向:把错误信息塞进查询串或用会话传递,都容易在并发与多标签场景下串号,直接渲染错误页更简单。