428 Precondition Required
用法
428 Precondition Required 状态码表示服务器要求此类 HTTP 请求必须是有条件的,并包含相应的 HTTP 头,例如 If-Match、If-None-Match、If-Modified-Since 和 If-Unmodified-Since。
这些 HTTP 头在服务器支持时可由客户端选择使用,但在本情形下服务器要求必须使用它们。视用例而定,这样做是为了减少诸如”更新丢失”(lost update)之类的数据丢失情况。
更新丢失发生在这种情形:客户端使用 GET 获取一个资源,修改该资源,然后使用 PUT 将修改后的版本发回服务器。若没有第三方干扰,就没有问题。但如果在初次 GET 之后,某个第三方修改了服务器上该资源的状态,就会产生冲突。如果客户端在没有先检查资源状态的情况下就使用 PUT,第三方的更新就会被覆盖(即更新丢失)。
通过使用 ETag 和日期强制执行条件测试,客户端覆盖更新的机会就会减少,因为对资源状态所做的假设更少了。
这与 412 Precondition Failed 响应相关,当一个或多个条件未通过时,服务器会返回后者。
SEO 影响
像 Google 这样的搜索引擎不会索引返回 428 状态的 URL。过去已被索引但返回此状态码的 URL 会从搜索结果中移除。
示例
客户端此前已获取该资源的一份副本,之后修改了本地副本。当客户端尝试 PUT 该资源以更新服务器时,服务器返回 428 Precondition Required,因为 HTTP 头中未包含条件测试。
请求
PUT /reservations.txt HTTP/1.1
Host: example.com
响应
HTTP/1.1 428 Precondition Required
Content-Type: text/html
Content-Length: 193
<html>
<head>
<title>Conditional Update Required</title>
</head>
<body>
<p>This PUT request must be done conditionally.
Try again using If-Unmodified-Since.</p>
</body>
</html>
如何修复
先用 GET 请求获取该资源,并从响应头中提取 ETag 值。然后重新发送修改性请求(PUT、PATCH 或 DELETE),并将 If-Match 头设置为取回的 ETag 值。
或者,使用 If-Unmodified-Since 头,配合初次 GET 响应中的 Last-Modified 日期。服务器会比较该日期,仅当资源自那时起未发生变化时才继续处理。
ETag 方式更精确。时间戳只有秒级粒度,会漏掉快速连续的编辑。而 ETag 无论时间如何,只要发生修改就会改变。
确保用于获取 ETag 的 GET 与后续的修改性请求针对同一资源。使用来自不同记录的 ETag 会触发 412 Precondition Failed 响应。
基于 SAP、Salesforce 等平台构建的 API 通常在更新和删除操作上强制要求条件请求。查阅 API 文档,了解哪些端点需要前置条件以及接受哪些校验头。
在服务器端,428 响应有助于防止”更新丢失”问题,即两个客户端编辑同一资源时互相覆盖。在所有可变资源上包含 ETag 头,能为客户端提供发起条件请求所需的值。
代码参考
.NET
HttpStatusCode.PreconditionRequired
Rust
http::StatusCode::PRECONDITION_REQUIRED
Rails
:precondition_required
Go
http.StatusPreconditionRequired
Symfony
Response::HTTP_PRECONDITION_REQUIRED
Python3.5+
http.HTTPStatus.PRECONDITION_REQUIRED
Apache HttpComponents Core
org.apache.hc.core5.http.HttpStatus.SC_PRECONDITION_REQUIRED
Angular
@angular/common/http/HttpStatusCode.PreconditionRequired