HTTP 428 与 412 的区别——使用 If-Match 防止并发更新覆盖

ネットワーク・RFCカテゴリを表すパンダのイラスト 网络・RFC

关于本文
本文是通过利用生成式 AI 的自动化流程创建的。我们于 2026 年 10 月 28 日重新确认了 RFC 6585 中关于 428 以及 RFC 9110 中关于 If-Match、ETag、412 的定义,并使用 Python 3.12.14 的状态模型校验了防止并发编辑覆盖事故的条件分支。未对实际存在的 API 执行更新请求。

验证状态:🧪 官方规范、状态模型校验、同一运行内的 5 角色自检 QA 已完成(外部 API / WordPress 未确认)
信息确认日期:2026 年 10 月 28 日(日本标准时间)

HTTP 428 与 412 的区别——使用 If-Match 防止并发更新覆盖

在由两个人编辑相同文档的 API 中,后保存的人的内容有时会抹去先保存的人的更改。通过使用 HTTP 的 If-Match,可以在请求中附加“仅在保持我读取的版本相同时才更新”的条件。如果服务器强制要求条件更新而未附加条件,则返回 428 Precondition Required;如果附加的条件与当前状态不匹配,通常返回 412 Precondition Failed。

这种区别不仅仅是记住错误代码那么简单。重要的是要理解这是一种防止数据丢失的协作关系:客户端记录“读取的是哪个版本”,服务器在更新前进行核对。

首先,思考更新丢失的场景

假设共享便笺的内容作为“初始草案”保存在一个虚构的文档 API 中。A 先生/女士和 B 先生/女士几乎同时执行 GET 并接收到相同的内容。在 A 保存了“A 的修改”之后,如果 B 基于旧的“初始草案”发起 PUT 发送“B 的修改”,在无条件覆盖的设计中,A 的更改可能会消失。这就是 lost update(更新丢失)。

例如,在仅通过 PUT 完全替换文档正文的 API 中,如果 B 在不知道 A 的更改的情况下发送从旧内容编辑的结果,往往会导致后写入者胜出。HTTP 本身并不能自动防止所有 API 的冲突。API 需要实现适当的条件更新,且客户端也需要发送相应的条件。

sequenceDiagram
    participant A as 編集者A
    participant B as 編集者B
    participant S as 文書API
    A->>S: GET /documents/42
    S-->>A: 200 + ETag "v7"
    B->>S: GET /documents/42
    S-->>B: 200 + ETag "v7"
    A->>S: PUT + If-Match "v7"
    S-->>A: 200 + 新ETag "v8"
    B->>S: PUT + If-Match "v7"
    S-->>B: 412 Precondition Failed
    Note over B,S: Bの古い版による上書きを防止

图中所示的ETag字符串是用于说明的虚构值。并不意味着实际的ETag会是v7、v8这样的连续编号。服务器会根据内部的版本管理、表现形式的内容等来生成标识符,但客户端无需推测值的生成规则。

通过GET接收ETag,并通过If-Match将其返回

ETag 是一个HTTP字段,用于识别服务器所选择的资源表现形式的版本。如果在GET响应中提供了ETag,客户端就可以保留该值。ETag既不是“加密数据”,也不是“用于用户认证的令牌”。它是用于比较内容是否相同的验证器(validator)。

假设虚拟的第一次GET响应如下。

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "v7"

{"title":"共有メモ","text":"初期案"}

A先生在编辑后,将通过GET接收到的ETag直接指定在 If-Match 请求头中。以下是一个展示通信形态的示例,并非已执行的API日志。

PUT /documents/42 HTTP/1.1
Host: example.com
Content-Type: application/json
If-Match: "v7"

{"title":"共有メモ","text":"Aの修正"}

服务器在应用更新处理之前,会比较当前的表现形式与If-Match的条件。如果当前版本一致,便可以继续更新。如果在A先生更新后版本变为了v8,那么B先生指定旧版v7的更新将会因条件不符而失败。

重要的是,客户端先进行GET操作这一事实本身并不能保证安全性 这一点。在GET之后到PUT之前的这段时间内,可能会插入其他更新。只有在服务器端妥善处理了条件确认与实际变更,以避免发生冲突时,它才有意义。如果应用程序允许“刚确认完就被其他处理覆盖”这种冲突,那么即使使用了If-Match也无法达成目的。

这种前提条件的评估顺序在 RFC 9110的条件请求 中进行了定义。

428表示“缺少前提条件”,412表示“前提条件失败”

即使编号相似,其原因和使用者接下来的行动也不同。

响应主要含义接下来要采取的行动
428 Precondition Required服务器要求提供条件请求获取最新版本并添加必要的条件
412 Precondition Failed指定的先决条件求值结果为假比较最新版本与自己的修改
409 Conflict当前资源状态与处理冲突检查API返回的冲突原因和解决步骤

如果忘记添加条件:428

如果服务器的策略是“更新本文档时必须指定 If-Match”,则它可以拒绝无条件的 PUT。

PUT /documents/42 HTTP/1.1
Host: example.com
Content-Type: application/json

{"title":"共有メモ","text":"変更案"}
HTTP/1.1 428 Precondition Required
Content-Type: application/json

{"error":"If-Match is required"}

此响应并不意味着“ETag 已过时”。而是指“希望设置条件,但缺少必要条件”的状态。RFC 6585 建议收到 428 的客户端说明如何重新发送请求。同时规定 428 响应不得保存在缓存中。

不过,并非所有服务器都强制使用 428。如果 API 允许无条件更新,它也可能会接受相同的请求。认为“没有返回 428 就说明已经实现了防冲突”是错误的。

依据:RFC 6585 §3 — 428 Precondition Required。

如果附加了过时的条件:412

A 的更新将当前的 ETag 变为 v8 后,如果 B 以 v7 作为条件发送 PUT,则与当前的表现形式不符。

PUT /documents/42 HTTP/1.1
Host: example.com
Content-Type: application/json
If-Match: "v7"

{"title":"共有メモ","text":"Bの修正"}
HTTP/1.1 412 Precondition Failed

412 表示“服务器评估的条件中至少有一个为假”。在常规的冲突场景中,服务器不会应用更新,而是让客户端检查当前版本。不过,由于 RFC 9110 中也存在当状态修改请求可被判定为已经成功时的例外情况,因此避免做出“只要不是 412 的响应就一定执行了新建更新” 这样的推测。

依据:RFC 9110 §13.1.1 — If-Match、§15.5.13 — 412。

If-Match 中重要的是“强比较”

ETag 除了用引号括起来的普通标签外,还有开头带有 W/ 的 弱 ETag。弱 ETag 用于表示表现形式在语义上等效等场景,这与要求字节级一致的场景是有区别的。

If-Match 的判定会使用 强比较。因此,以下两者在 If-Match 中无法匹配:

ETag: "v7"
If-Match: W/"v7"

即使表面上的识别字符串相同,带 W/ 的标签也不能与强 ETag 等同视之。如果在 API 的更新控制中使用 If-Match,则必须确认 GET 返回的 ETag 的强弱以及服务器的比较实现。当向 If-Match 传入弱 ETag 导致返回 412 时,不能简单地去除引号或擅自删除 W/ 进行处理。应当重新获取当前的表现形式,并使用 API 指定的正规验证器。

此外,If-Match: * 虽然可以表示“当前表现形式存在”的条件,但并不要求与特定的版本一致。存在性确认与并发编辑的版本冲突检测是两个不同的问题。为了防止更新丢失,不应轻率地将其替换为星号。

依据:RFC 9110 §8.8.3 — ETag、§13.1.1 — If-Match。

收到 412 时切勿“重新发送相同的 PUT”

出现412时最大的危险是,在未解决冲突的情况下仅将条件替换为最新的ETag,并重新发送相同的旧正文。这可能会无意中覆盖原本想要保护的A先生的修改。

在一个安全的业务流程中,首先通过GET获取最新版本及其ETag。接着确认与自己编辑的内容之间是否存在差异,如果发生修改冲突,应由人工进行选择,或者使用API定义合并处理。在合并后的内容确定后,带上获取到的最新版ETag进行更新。即使收到更新成功的响应,也应保存API返回的新版本信息,并在需要时重新读取进行确认。

例如,如果A先生修改了“审批人”,而B先生修改了“说明文字”,则可能保留这两处的修改。另一方面,如果对同一个“审批人”字段修改成了不同的值,自动选择其中一个就很危险。412是检测冲突的入口,并不是决定正确合并策略的机制。

它与409或304有什么不同?

409 Conflict 是由于与当前资源状态冲突而无法完成请求时更常见的响应。例如,当业务规则上的状态转换不被允许时,它会被用于除了ETag不匹配之外的其他原因。虽然返回哪个状态取决于API设计,但412对应的是“If-Match前提条件为假”这种具体情况。

304 Not Modified 主要用于缓存重新验证的响应。例如,不要将GET中使用If-None-Match的含义与PUT中使用If-Match的含义混淆。即使利用了ETag这一公共验证器,目的也会因头部和方法的组合而改变。

如果死记硬背“因为有ETag所以总是304”,“因为ETag旧所以是428”,是无法排查故障的。发送了哪个HTTP方法以及哪个条件头部 先确认这一点是捷径。

在实际业务中设计API时的核对事项

对于共享文档或配置管理等更新API,应在API契约中明确规定是允许无条件的PUT,还是通过428拒绝。如果采用条件更新,可以设计为:GET响应返回可用的ETag,PUT时检查If-Match,更新成立后返回下一个版本信息。还需要确认更新和比较是一体成立的,且数据库更新时的冲突没有被漏掉。

在故障调查中,按以下顺序整理记录就能找出原因。

  1. 更新前的GET接收到了哪个ETag。

  2. 更新请求中是否带有If-Match。

  3. 发送的值是强ETag还是弱ETag。

  4. 响应是428、412还是409中的哪一个。

  5. 同一时间是否有其他更新存在。

  6. 更新失败后是否自动重新发送了旧正文。

无需在操作日志中无条件地大量记录 ETag 或文档内容。对于包含敏感信息的 API,应采取仅保留关联 ID、状态、匿名化版本标识等必要最少信息的方针,切勿将认证信息或文档正文输出到日志中。ETag 不能替代认证与授权,访问权限的检查仍然必不可少。

此外,HTTP 的条件更新并不能保证跨多个资源的整个业务事务的一致性。即使能够安全地更新单个文档,与其他文档的相互关系、通知的重复、对外部系统的副作用等问题,仍然属于应用程序端设计需要解决的课题。

更改一处来思考

在最初的例子中,如果将 B 先生的 If-Match 从 v7 改为 v8,就会变成与当前版本相匹配的条件。但是,切勿在未重新编辑 B 先生正文的情况下仅更改条件。这只是为了理解条件含义而进行的思想实验,并非推荐的重试步骤。

另外,如果直接删除 If-Match 会怎么样呢?对于必须进行条件更新的 API,预计会返回 428;而对于允许无条件更新的 API,覆盖操作可能会成功。也就是说,428 代表的是“要求提供条件的服务器方针”,而 412 代表的是“所发送条件的评估结果”,二者处于不同的阶段。

通过状态模型确认的边界值

2026年10月28日,我们使用 Python 3.12.14 的内存状态模型重新核算了正文的分支。这并非针对实际服务的通信测试,而是仅提取 ETag 和 If-Match 条件进行的验证。

条件预期结果观测结果
条件必填的更新中缺少 If-Match428,正文保持不变一致
当前的强 ETag "v7"200,更新正文一致
更新后复用旧的 "v7" 412,保留先前更新一致
弱 ETag W/"v8"412,正文未更改一致
重新获取最新版本并使用 "v8" 进行更新200,更新为合并后的正文一致
服务器不强制要求条件可能会接受无条件更新一致

所有 6 种情况均符合预期。不过,尚未验证 HTTP 服务器、数据库原子更新、代理以及实际 API 的 ETag 生成规则。不能将模型的成功直接作为目标系统实现的保证。

总结

HTTP 的 428 表示需要条件请求,412 表示发送的条件未满足。通过使用 If-Match 和强 ETag,可以实现“仅在读取的版本未发生变化时才更新”的设计。但这并不意味着会自动解决冲突后的合并、授权以及多个资源的完整性。

如果更新发生冲突,切勿移除条件重新发送,而是应该获取最新版本并确认差异。正是这多出的一步,将 HTTP 状态码与实际的数据保护联系在了一起。

官方与一手资料

※通信示例为说明目的而构建。除 Python 状态模型检查外,未对实际服务进行更新测试、响应收集或 WordPress 渲染确认。

文档信息

文章??
HTTP 428 与 412 的区别——使用 If-Match 防止并发更新覆盖
?布日期
更新日期
来源
https://papanda925.com/?p=18254&lang=zh

?可: ?于本站?有相??利的正文及原??表,除非?有?明,可依据 CC BY 4.0 使用。本文可能包含使用生成式AI?建或??的内容。若代??有?可声明,或?接的GitHub???定了?可,?代?以??可?准。引用内容、第三方?料、?片及商?不在本?可范?内。 使用政策

标题和URL已复制