理解与应用:Post/Redirect/Get (PRG) 模式

kayokoi 发布于 2025-05-14 57 次阅读


一、问题的提出:刷新 POST 请求结果页的风险

当用户通过浏览器提交表单(例如,在线购物下订单)时,如果服务器在处理完 POST​ 请求后直接返回结果页面,可能会引发一系列问题,尤其是在用户刷新页面时。

  1. 场景描述:

    • 用户填写表单(如选择商品、填写地址)并点击“提交订单”。
    • 浏览器将订单信息打包成一个 POST​ 请求发送给服务器。
  2. 传统(非PRG)处理流程及风险:

    • 服务器处理 POST​ 请求(如在数据库创建订单记录)。

    • 服务器直接返回一个显示“订单提交成功!”的 HTML 页面作为对该 POST​ 请求的响应。

    • 此时,浏览器地址栏显示的 URL 可能仍是提交订单时的 URL(例如 www.example.com/submitOrder​)。

    • 浏览器“记住”了它最后一次与服务器的成功交互是一个 POST​ 请求,以及该 POST​ 请求所携带的数据。

    • 灾难发生:如果用户此时:

      • 不小心按了 F5 键刷新页面。
      • 点击了浏览器的“刷新”按钮。
      • 网络卡顿后,浏览器自动重试上一个请求。
    • 结果:浏览器会尝试重新发送上一次的 POST​ 请求,将订单信息原封不动地再次提交给服务器。这可能导致:

      • 订单被重复创建。
      • 用户被重复扣款。
      • 其他意外的重复操作。
    • 许多浏览器在这种情况下会弹出一个警告,询问用户是否确定要重新提交表单数据,正是为了防止此类问题。

  3. 核心原因:
    浏览器“刷新”页面的行为,本质上是指示浏览器重新执行获取当前页面内容所进行的最后一次成功的网络请求。如果这最后一次请求是 POST​,则 POST​ 操作会被重复。

二、PRG 模式:优雅的解决方案

“Redirect After Post” (PRG) 模式通过一个巧妙的流程来避免上述问题。

  1. PRG 模式核心流程:

    • 步骤 1:填写表单,浏览器发送 POST​ 请求

      • 与之前相同,用户提交表单,浏览器发送 POST​ 请求。
    • 步骤 2:服务器处理 POST​ 请求

      • 服务器接收到 POST​ 请求,并处理相关业务逻辑(如保存订单到数据库)。
    • 步骤 3:服务器发送重定向响应(核心!)

      • 订单处理成功后,服务器不直接渲染和返回 HTML 页面给当前的 POST​ 请求。
      • 相反,服务器向浏览器发送一个 HTTP 重定向响应(状态码通常是 302 Found​ - 临时重定向,或更推荐用于 PRG 的 303 See Other​)。
      • 此重定向响应的头部信息中,包含一个 Location​ 字段,该字段的值是一个新的 URL(例如 www.example.com/orderConfirmation?orderId=12345​),指向一个专门用于展示订单确认信息的页面。
    • 步骤 4:浏览器向新 URL 发起 GET​ 请求

      • 浏览器收到重定向响应后,会读取 Location​ 头中的新 URL。
      • 浏览器自动地、立刻地向这个新的 URL 发起一个全新的 GET​ 请求。
    • 步骤 5:服务器处理 GET​ 请求,返回结果页面

      • 服务器收到这个 GET​ 请求。
      • 根据请求中的参数(如 orderId=12345​),服务器从数据库检索订单信息。
      • 服务器渲染一个“订单提交成功!”的 HTML 页面,并将其作为对这个 GET​ 请求的响应返回给浏览器。
  2. 刷新 PRG 后的结果页面:

    • 现在,浏览器地址栏显示的是 GET​ 请求的 URL(如 www.example.com/orderConfirmation?orderId=12345​)。
    • 浏览器“记住”的最后一次成功请求是这个 GET​ 请求。
    • 如果用户刷新页面,浏览器会重新发送它上一次的请求,即对 www.example.com/orderConfirmation?orderId=12345​ 的 GET​ 请求。
    • ​GET​ 请求通常被认为是“安全的”(不应改变服务器状态)和“幂等的”(多次执行结果相同,无额外副作用)。
    • 服务器再次收到这个 GET​ 请求,只会再次查询订单信息并显示,不会再次创建新的订单。

三、PRG 模式的形象比喻

  • 没有 PRG(直接响应 POST​):

    • 你写信(订单信息)交由邮局柜员(服务器处理 POST​)。
    • 柜员当场处理完,把盖章的回执直接给你(服务器直接返回成功页面给 POST​ 请求)。
    • 若不慎丢失回执再回柜台(刷新页面),你可能不自觉地再次递交信件内容(重新 POST​),导致重复处理。
  • 使用 PRG(Redirect After Post):

    • 你写信交由邮局柜员(服务器处理 POST​)。
    • 柜员处理完后,不直接给回执,而是给你一张小纸条,写着:“请到旁边的A窗口凭凭证号XXX领取回执”(服务器发送重定向,Location​ 指向新 URL)。
    • 你拿纸条到A窗口(浏览器发起 GET​ 请求到新 URL)。
    • A窗口员工根据凭证号把回执给你(服务器响应 GET​ 请求,返回成功页面)。
    • 现在,若不慎丢失回执,你只会拿凭证号再到A窗口询问(刷新 GET​ 页面),A窗口员工只会再次出示你的回执,绝不会因此重新帮你寄信。

四、PRG 模式的优点

  1. 防止重复提交:这是最核心的好处,有效解决了刷新 POST​ 结果页面导致操作被重复执行的问题。

  2. URL 更友好:用户浏览器地址栏最终显示的是一个 GET​ 请求的 URL。这个 URL 通常可以被安全地:

    • 收藏为书签。
    • 分享给他人。
    • 多次刷新而不会产生副作用。
  3. 改善用户体验:浏览器的后退、前进、刷新按钮的行为更符合用户的直觉和预期。

  4. 广泛适用性:PRG 模式是 Web 开发中的重要实践,特别推荐在处理任何会改变服务器状态的用户提交(如创建、更新、删除操作)时使用。

五、深入理解:浏览器刷新机制与 PRG 的关系

  1. 浏览器“刷新”的本质:
    刷新操作指示浏览器重新执行获取当前页面内容所进行的最后一次成功的网络请求。

  2. 浏览器“记住”的内容:
    浏览器会记录最后一次成功 HTTP 请求的详细信息,包括请求的 URL、请求的方法(GET​ 或 POST​),以及(如果是 POST​ 请求)当时一起发送的数据。

  3. PRG 如何改变浏览器的“记忆”:

    • 在没有 PRG 的情况下,显示结果页的最后请求是 POST​。刷新时,这个 POST​ 被重复。
    • PRG 模式通过在服务器处理完 POST​ 请求后,并不直接返回内容,而是发送一个重定向指令。浏览器遵从这个指令,发起一个新的 GET​ 请求去获取最终的页面内容。
    • 因此,当最终页面显示时,浏览器“记住”的最后一次成功请求是一个 GET​ 请求。刷新页面时,重复的是这个无副作用的 GET​ 请求。

六、总结

Post/Redirect/Get (PRG) 模式是一种关键的 Web 开发设计模式。它通过在服务器处理完客户端的 POST​ 请求后,将客户端重定向到一个新的 URL(通常通过 GET​ 请求访问该 URL 以获取结果页面),从而巧妙地解决了因用户刷新页面而可能导致的表单重复提交问题。PRG 模式不仅增强了应用的健壮性,还提升了 URL 的友好性和整体用户体验。