412 Precondition Failed
使い方
412 Precondition Failed のエラーメッセージを受け取ったとき、クライアントはリクエストで指定した1つ以上の条件が満たされず、リソースが期待された状態になかったと認識します。具体的な挙動は前提条件のヘッダーによって異なります。If-Match の失敗は、どのメソッドでも 412 を返します。If-None-Match の失敗は、GET と HEAD では 304 を、それ以外のすべてのメソッドでは 412 を返します。
これは 304 と似ていますが、304 の場合はクライアントがすでに最新バージョンを持っていて再送信が不要であるためにリソースが送られなかった、という点が異なります。
SEO への影響
Google のような検索エンジンは、412 のレスポンスステータスを持つ URL をインデックスしません。以前にインデックスされていた URL は検索結果から削除されます。
例
クライアントはブログ投稿に返信したいと考えていますが、その投稿がクライアントが最後にリソースを閲覧して以降変更されていない場合に限ります。この条件は If-Unmodified-Since ヘッダーの一部として含まれます。条件が満たされない場合、リソースは変更されているため、サーバーはリクエストが完了しなかったことを示す 412 Precondition Failed で応答します。
このプロトコルに従うことで、「ロストアップデート」の問題が回避されます。ロストアップデートは、複数のクライアントが同じリソースに書き込み、そのうち1つ以上が古いバージョンで作業している場合に発生します。リソースが変更されていないことを保証することで、その間に変更されたデータの上書きが防がれます。
リクエスト
POST /blog/update?postid=111&task=reply HTTP/1.1
Host: example.com
If-Unmodified-Since: Fri, 1 Jan 2021 00:00:00 GMT
Content-Type: text/plain
Content-Length: 45
<Message body contains reply-text from the client>
レスポンス
HTTP/1.1 412 Precondition Failed
Content-Type: text/html
Content-Length: 139
<html>
<head>
<title>Blog Update Error</title>
</head>
<body>
<p>The post has changed since last viewed.</p>
</body>
</html>
対処方法
GET または HEAD リクエストでリソースの現在のバージョンを取得し、最新の ETag または Last-Modified の値を入手します。If-Match または If-Unmodified-Since ヘッダー内の古いバリデーターを現在のものに置き換えて、リクエストを再試行します。
楽観的並行性制御(REST API や CMS プラットフォームで一般的)を使う場合、ワークフローは次のとおりです。
ブラウザのキャッシュを消去すると、古いキャッシュ済みバリデーターに起因する 412 エラーが解消します。ブラウザが古い ETag やタイムスタンプを保持しており、古い条件付きヘッダーを自動的にリクエストへ付加しているためです。
クライアントとオリジンサーバーのあいだにあるキャッシュプロキシや CDN が、古いバリデーターを提供することがあります。プロキシの設定が条件付きヘッダーを変更せずに通過させていることを検証してください。たとえば AWS CloudFront は、GET リクエストの If-Match ヘッダーがオブジェクトの ETag と一致しない場合に 412 を返します。こうした場合は、CDN のキャッシュが新鮮であることを確認するか、ディストリビューションを無効化(invalidate)します。
412 と 409 の比較
412 と 409 はどちらも状態の競合を示しますが、原因が異なります。412 レスポンスは、前提条件のヘッダー(If-Match または If-Unmodified-Since)が偽と評価されたことを意味します。409 レスポンスは、条件付きヘッダーとは無関係にリクエストがリソースの現在の状態と競合していることを意味します。ETag やタイムスタンプに基づく楽観的並行性のチェックには 412 を使います。重複エントリや無効な状態遷移といったビジネスロジック上の競合には 409 を使います。
コードリファレンス
.NET
HttpStatusCode.PreconditionFailed
Rust
http::StatusCode::PRECONDITION_FAILED
Rails
:precondition_failed
Go
http.StatusPreconditionFailed
Symfony
Response::HTTP_PRECONDITION_FAILED
Python3.5+
http.HTTPStatus.PRECONDITION_FAILED
Java
java.net.HttpURLConnection.HTTP_PRECON_FAILED
Apache HttpComponents Core
org.apache.hc.core5.http.HttpStatus.SC_PRECONDITION_FAILED
Angular
@angular/common/http/HttpStatusCode.PreconditionFailed