409 Conflict
用法
收到 409 Conflict 错误时,请求本身是有效的,但由于状态不匹配而无法完成。例如,客户端回复了一个已被删除的帖子,目标资源已不再处于兼容状态。服务器返回此状态以示冲突。
注意
409 Conflict 意味着请求格式正确,但与当前资源状态冲突(并发更新、重复创建)。422 Unprocessable Content 意味着请求体语法有效但语义无效(未通过校验规则)。400 Bad Request 意味着请求本身畸形(消息体无法解析、头无效)。409 关乎状态冲突,422 关乎校验,400 关乎语法。
客户端应当检查消息体以确定如何解决问题。在回复已删除帖子的情形中,客户端可选择放弃请求、恢复已删除的原帖,或引用原文创建一个新帖。此操作完全由客户端负责,遵循服务器设定的要求或策略。
SEO 影响
像 Google 这样的搜索引擎不会索引返回 409 状态的 URL。此前已被索引、如今返回该状态码的 URL 会从搜索结果中移除。
示例
客户端尝试为一篇 “ID = 111” 的现有博客帖子添加一个 “like” 标签。服务器作出响应,表明该帖子已被冻结,不再接受回复或标签。
请求
POST /blog/update?postid=111&flag=like HTTP/1.1
Host: example.com
响应
HTTP/1.1 409 Conflict
Content-Type: text/html
Content-Length: 288
<html>
<head>
<title>Blog Entry Frozen</title>
</head>
<body>
<p>This post has been marked read-only. To post
a reply or tag, change the status and try
again.</p>
</body>
</html>
如何修复
409 Conflict 意味着请求与服务器上资源的当前状态冲突。
在重试前获取当前的资源状态。发送 GET 请求以获取最新版本。自客户端上次读取数据以来,资源状态已发生变化。将返回的 ETag 或版本字段与客户端持有的值进行比较。
使用条件头来检测冲突。在 PUT 和 PATCH 请求中包含带当前 ETag 值的 If-Match。当 ETag 不再匹配时,服务器会拒绝请求,从而防止盲目覆盖。这种模式称为乐观并发控制。
PUT /api/resource/42 HTTP/1.1
If-Match: "abc123"
Content-Type: application/json
{"name": "updated value"}
当另一个客户端在 GET 与 PUT 之间修改了资源时,服务器返回 409。
合并变更或实现冲突解决逻辑。将客户端预期的变更与当前的服务器状态进行比较。应用合并策略:后写入者获胜、字段级合并,或提示终端用户解决差异。在载荷中带有 version 或 revision 字段的 API,要求客户端在更新请求中包含当前版本号。
解决重复资源冲突。使用 POST 或 PUT 创建资源时,若已存在具有相同唯一标识符的资源,服务器返回 409。在创建前检查现有条目,并使用幂等键以避免重试时的重复创建。
处理数据库级别的冲突。当唯一约束、外键约束或行级锁阻止操作时,由数据库支撑的应用服务器返回 409。响应体通常包含具体的约束名称。在重试前解决底层的数据冲突(重复键、缺失父记录、并发事务)。
对于版本控制系统,拉取最新版本并重新应用变更。获取最新修订、协调差异并重新提交更新。基于 Git 的 API(GitHub、GitLab)在推送与上游变更冲突时返回 409。
检查响应体中的冲突细节。服务器通常会包含解释不匹配的信息:冲突的字段、当前值或期望的版本。使用这些数据来确定正确的解决路径。
实现带状态刷新的重试逻辑。对于自动化客户端,构建一个重试循环:获取最新的资源状态、重新应用预期的修改,并重新提交。添加指数退避,以避免在高并发条件下压垮服务器。
代码参考
.NET
HttpStatusCode.Conflict
Rust
http::StatusCode::CONFLICT
Rails
:conflict
Go
http.StatusConflict
Symfony
Response::HTTP_CONFLICT
Python3.5+
http.HTTPStatus.CONFLICT
Java
java.net.HttpURLConnection.HTTP_CONFLICT
Apache HttpComponents Core
org.apache.hc.core5.http.HttpStatus.SC_CONFLICT
Angular
@angular/common/http/HttpStatusCode.Conflict