はじめに
REST APIのリクエスト・レスポンスヘッダにEtagやIf-Matchという項目がついていることがあります。
この記事では、HTTP標準の仕組みである ETag と If-Match について、
- ETag と If-Matchとは何なのか
- どうやって「上書き事故」を防ぐのか(楽観ロック / 楽観的排他制御)
- If-Match を付けたら CORS エラーになる?
を整理します。
ETag と If-Match とは
| ヘッダ | 誰が送る? | 役割 |
|---|---|---|
ETag |
サーバ → クライアント | リソースの「状態(バージョン)」を表す識別子 |
If-Match |
クライアント → サーバ | 「この ETag のままなら更新して」という条件 |
通常、ETag はレスポンスヘッダ、If-Match はリクエストヘッダとして使います。
なぜ必要?
例として「ユーザーA/Bが同じデータを編集する」ケースを考えます。
- ユーザーAが古い内容で更新を送信
- その間にユーザーBが先に更新していた
- AがBの更新を知らずに上書きしてしまう
この事故を防ぐのが ETag + If-Match です。
基本フロー(GET → 更新)
1) まず GET して ETag を受け取る
GET /items/123 HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "v3"
{
"id": 123,
"name": "Item A"
}
2) 更新時に If-Match を付ける(PUT/PATCH/DELETE)
PATCH /items/123 HTTP/1.1
Content-Type: application/json
If-Match: "v3"
{
"name": "Item A Updated"
}
- サーバ側の最新 ETag が "v3" のままなら更新OK
- すでに "v4" などに変わっていたら更新拒否(上書き事故を防止)
成功パターン:ETag が一致
HTTP/1.1 204 No Content
ETag: "v4"
更新後は ETag が進む/変わることが多いので、返しておくと親切です。
失敗パターン:ETag が不一致
別の誰かが先に更新して ETag が変わっていた場合:
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
{
"message": "Resource has been modified. Fetch latest and retry."
}
この 412 を返せることで、クライアントは
- 最新を取り直す
- 差分マージのUIを出す
- ユーザーに「他で更新されました」を通知する
などの安全な対応ができます。
If-Match を付けたら CORS エラーになる?
If-Match リクエストヘッダにつけるため、CORSエラーになる場合もあります。
プリフライト例:
OPTIONS /items/123 HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: if-match, content-type
ここに対してサーバが許可するヘッダを返さないと、ブラウザがブロックして CORS エラーになります。
例:
Access-Control-Allow-Headers: Content-Type, Authorization, If-Match
If-Match 自体が CORS エラーを「起こす」わけではありません。
クロスオリジン時、ブラウザはプリフライトで送ってよいヘッダの許可リストを確認します。
その許可リスト(Access-Control-Allow-Headers)に If-Match が含まれていないと、ブラウザがブロックして CORS エラーになります。
フロントからETagを読み取るには
「If-Match を送れるようにした」だけだと、フロント側でレスポンスの ETag が読めないことがあります。(開発者ツールでは見れるのに、javascriptで読み取れないことなど)
一般に JavaScript から参照できるレスポンスヘッダは、CORS-safelisted response headers にデフォルトで制限されており、ETagは含まれません。
そのため、クロスオリジン通信でres.headers.get("ETag")のように取得しようとしても null になります。
ETag をブラウザの JS から読めるようにするには、サーバが Access-Control-Expose-Headers で明示的に公開する必要があります。
Access-Control-Expose-Headers: X-Custom-Header, ETag
参考
https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/ETag
https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/If-Match
https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Headers
https://developer.mozilla.org/ja/docs/Glossary/CORS-safelisted_response_header