跳到正文
效率工具实用笔记关于本站
浏览器工具

303 See Other用在什么时候:POST之后重定向为什么能防止重复提交

约 7 分钟阅读AI 辅助整理

303 See Other 用于 PUT 或 POST 请求完成之后重定向,它会把其他方法变更为 GET 且消息主体丢失。这样刷新页面只会重新加载结果页,不会重复提交原来的不安全请求。

本文要点

  1. 303 被单独列出来,是因为它对请求方法的处理方式和别的重定向码不一样

  2. 要理解 303 为什么能防止重复提交,先要理解刷新按钮做了什么

  3. 303 不只用在“提交完立刻看结果”这一种情况

303 See Other用在什么时候:POST之后重定向为什么能防止重复提交的知识要点示意图
依据已审核正文摘取要点制作的示意图,非实物照片;请结合正文中的条件与范围阅读。要点摘自已审核正文;完整条件与说明见正文。查看高清图解 ↗
本文目录

先回答:303 是给“已经改完数据”的请求用的

如果你刚用 POST 提交了一个表单,或者用 PUT 更新了一条记录,服务器处理完之后想让你看到一个结果页,这时该用哪个重定向状态码?答案是 303 See Other。

按 MDN 的重定向文档,303 的典型应用场景写得很明确:用于 PUT 或 POST 请求完成之后重定向,来防止由于页面刷新导致的操作的重复触发。

这句话里有两个限定词要抓住。第一,它针对的是 PUT 或 POST 这类会修改服务器端状态的请求。第二,它出现在“请求完成之后”,也就是数据已经改完了,不是改之前。

所以判断顺序可以这样定:先看这次请求是不是已经完成了写操作,再看你是不是想让用户跳到一个新的结果页。两个条件都满足,303 才是对的选择。本文只讲 303 在 PUT 或 POST 完成后的重定向行为,以及它为什么能避免刷新重复提交。不讲 301 与 302 在方法保留上的差异,也不讲 307。

303 会把其他方法变成 GET,消息主体不再带过去

303 被单独列出来,是因为它对请求方法的处理方式和别的重定向码不一样。

按同一份 MDN 文档的表格,303 See Other 的处理方法写的是:GET 方法不会发生变更,其他方法会变更为 GET 方法(消息主体丢失)。

把这句话拆成两半。前半句是:如果原来就是 GET,那还是 GET。后半句是:如果原来是 POST、PUT 或其他方法,重定向之后浏览器发出的新请求会变成 GET,而且原来那个请求的消息主体不会再跟着发过去。

“消息主体丢失”这个说法容易让人误会成数据丢了。它说的是重定向之后那次新请求不带原来的请求体,而不是服务器没收到你提交的内容。你提交的数据在第一次请求时已经到达服务器并被处理了,303 只是让接下来的那次跳转用 GET 去取结果页。

一个自建示例(练习场景,不是对某个真实站点的记录):假设你在练习目录里维护一个自建表单页,提交后服务器返回 303,Location 指向一个结果页。浏览器接着用 GET 请求那个结果页,地址栏变成结果页的地址,原来的表单数据不再随请求发送。

刷新按钮为什么不再重复提交:浏览器重放的是 GET

要理解 303 为什么能防止重复提交,先要理解刷新按钮做了什么。

按 MDN 文档对不安全请求临时响应的说明:不安全的请求会修改服务器端的状态,应该避免用户无意的重复发送它们。通常,你并不想要你的用户重复发送 PUT、POST 或 DELETE 请求。假如你为该类请求返回响应的话,简单地点击刷新按钮就会导致请求的重复发送(可能在确认消息之后)。

这段话描述的是没有 303 的情况:服务器直接返回一个普通响应,浏览器地址栏还停在那个 POST 地址上,用户一按刷新,浏览器就会把刚才那个 POST 再发一遍。

文档接着给出了 303 的做法:在这种情况下,服务器可以为 URL 发回一个 303(See Other)响应,其中含有正确的响应信息。如果刷新按钮被点击的话,只会导致该页面被刷新,而不会重复提交不安全的请求。

原因就在于上一步说的方法变更。经过 303 之后,浏览器当前停留的页面是通过 GET 拿到的结果页。用户再按刷新,重放的是这个 GET 请求,而不是最初那个 POST。GET 不修改服务器状态,所以刷新多少次都不会再触发一次写操作。

一个自建示例(练习场景):你在练习目录里放一个自建表单,提交后服务器返回 303 跳到 exercise/result.html。你在结果页上连按几次刷新,浏览器每次都是重新 GET 这个结果页,不会重新 POST 表单。

耗时请求也可以用 303:先给一个进度页

303 不只用在“提交完立刻看结果”这一种情况。

按 MDN 文档对耗时请求临时响应的说明:一些请求的处理会需要比较长的时间,比如有时候 DELETE 请求会被安排为稍后处理。在这种情况下,会返回一个 303(See Other)重定向响应,该响应链接到一个页面,表示请求的操作已经被列入计划,并且最终会通知用户操作的进展情况,或者允许用户将其取消。

这个场景和上一个场景的共同点是:写操作已经受理,用户接下来看到的应该是一个可以安全刷新的页面。区别在于,上一个场景的结果页展示的是“改完了”,这个场景的进度页展示的是“已经排队,正在处理”。

两种情况下,303 起的作用是一样的:把用户从那个会触发写操作的地址,带到另一个用 GET 就能看的地址上。所以判断要不要用 303,不取决于请求是快是慢,而取决于这次请求是不是已经完成了对服务器状态的修改或受理。

用自建文件核对一次 303 的跳转结果

下面是一个自建练习场景,用来观察 303 前后请求方法的变化。所有文件都放在练习目录里,不要拿正在用的项目直接试。

第一步,在练习目录里新建一个自建表单页,例如 exercise/form.html,里面只放一个提交按钮,提交到 exercise/submit 这个练习地址。

第二步,在练习用的服务端代码里,让 exercise/submit 收到 POST 之后返回 303,并在 Location 里写上 exercise/result.html。

第三步,用浏览器打开表单页,提交一次,然后在开发者工具的 Network 面板里看两条请求:第一条是 POST exercise/submit,状态码 303;第二条是 GET exercise/result.html,状态码 200。

第四步,在结果页上按一次刷新,再看 Network 面板。这时应该只出现一条 GET exercise/result.html,不会再出现 POST。

这个核对方法只观察请求方法和状态码,不修改任何系统设置,也不涉及真实数据。

什么时候不该用 303:它不负责保留原方法

303 的设计目的就是把方法变成 GET,所以如果你的需求是“重定向之后仍然用原来的方法再发一次”,303 就不合适。

按 MDN 文档的表格,303 的处理方法写的是“其他方法会变更为 GET 方法(消息主体丢失)”。这句话是确定的,不是可选项。也就是说,用了 303,就不能指望原来的 PUT 或 POST 被原样重放。

本文不展开讲 307 和 308 在这方面的区别,只提醒一点:选状态码之前,先想清楚你要的是“跳到一个能安全刷新的结果页”,还是“用同样的方法再请求一次新地址”。前者对应 303 的适用场景,后者不在本文范围内。

另一个限制来自文档对重定向本身的提醒:每一次重定向都会带来性能上的开销,应该尽可能地限制其使用数量。303 虽然能防止重复提交,但它仍然是一次额外的往返,所以不要把它当成可以随意叠加的常规手段。

观看知识要点视频 18 秒

18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。

参考资料

以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。

请结合原始资料的适用条件阅读。

返回浏览器工具 →