428 Precondition Required
使い方
428 Precondition Required ステータスコードは、この種の HTTP リクエストが条件付きであり、If-Match、If-None-Match、If-Modified-Since、If-Unmodified-Since といった適切な HTTP ヘッダーを含むことをサーバーが期待していることを伝えます。
これらの HTTP ヘッダーは、サーバーがサポートしている場合にクライアントが任意で使うものですが、この場合はサーバーがその使用を要求しています。ユースケースによっては、これは「ロストアップデート」問題のようなデータ損失の発生を減らすことを目的としています。
ロストアップデートは、クライアントが GET でリソースを取得し、それを変更し、変更後のバージョンを PUT でサーバーへ送り返す際に発生します。第三者の干渉がなければ問題ありません。最初の GET のあとに第三者がサーバー上のリソースの状態を変更していた場合、競合が生じます。クライアントがリソースの状態を先に確認せずに PUT を使うと、第三者の更新が上書きされます(ロストアップデート)。
ETag と日付を使った条件付きのテストを強制することで、リソースの状態について行われる仮定が減るため、クライアントが更新を上書きしてしまう機会が低減します。
これは、条件のうち1つ以上が満たされなかったときにサーバーが送信する 412 Precondition Failed レスポンスに関連します。
SEO への影響
Google のような検索エンジンは、428 のレスポンスステータスを持つ URL をインデックスしません。このステータスコードを返す、以前にインデックスされていた URL は検索結果から削除されます。
例
クライアントは以前にリソースのコピーを取得しており、その後ローカルのコピーを変更しました。クライアントがそのリソースを PUT してサーバーを更新しようとすると、HTTP ヘッダーに条件付きのテストが含まれていないため、サーバーは 428 Precondition Required で応答します。
リクエスト
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 の値を抽出します。次に、取得した ETag の値を設定した If-Match ヘッダーを伴って、変更を行うリクエスト(PUT、PATCH、DELETE)を再送信します。
代替として、最初の GET レスポンスの Last-Modified の日付を使って If-Unmodified-Since ヘッダーを利用します。サーバーは日付を比較し、それ以降リソースが変更されていない場合にのみ処理を進めます。
ETag を使う方法のほうが精密です。タイムスタンプの粒度は秒単位であり、短時間に連続する編集を取りこぼします。ETag はタイミングに関係なく、変更のたびに変わります。
ETag を取得するために使った GET が、その後の変更リクエストと同じリソースを対象としていることを確認してください。別のレコードの ETag を使うと、412 Precondition Failed レスポンスが発生します。
SAP、Salesforce、および類似のプラットフォーム上に構築された API は、更新や削除の操作で条件付きリクエストを強制することがよくあります。どのエンドポイントが前提条件を必要とし、どのバリデーターヘッダーが受け入れられるかは API ドキュメントで確認してください。
サーバー側では、428 レスポンスは、同じリソースを編集する2つのクライアントが互いを上書きしてしまう「ロストアップデート」問題の防止に役立ちます。変更可能なすべてのリソースに 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