ゆるテックノート

条件付きリクエストとETag活用

同時更新の衝突を避けたり、無駄な転送を減らしたりするのに役立つのが条件付きリクエストです。RFC 9110の挙動を踏まえて、APIで使いやすい形に整理しました。

主要ヘッダー

目的ごとにヘッダーが分かれています。ETagは強いタグ("abc")と弱いタグ(W/"abc")の2種類があります。

条件付きヘッダー一覧

ヘッダー 用途 一致時の返却例
If-None-Match キャッシュ再利用チェック。GETで一致したら304。POST/PUTで一致したら412で「すでにある」を示す用途にも。 GET: 304 / POST: 412
If-Modified-Since 最終更新日時でキャッシュ確認。ETagが使えないときのフォールバック。 GET: 304
If-Match 一致したときだけ更新・削除を許可。楽観ロックに使う。 PUT/PATCH/DELETE: 不一致なら412
If-Unmodified-Since 一定時刻以降に変わっていないことを要求。If-Matchが使えない場合の代替。 PUT/PATCH/DELETE: 412
If-Range 部分取得の再開時に使う。ETagか日時が一致しない場合は通常の200を返して全体送信。 GET (Range): 206 or 200

よくある使い方

キャッシュ削減と衝突回避の2軸で覚えると実務で迷いません。

パターン別フロー

  • キャッシュ確認: GETにIf-None-Matchを付け、ETagが一致したら304を返す。レスポンスのETagは必ず送る。
  • 新規作成の衝突回避: POSTにIf-None-Match: * を付け、存在したら412で拒否(RFC 9110 13.1.2)。
  • 更新の楽観ロック: クライアントが事前にGETしたETagをIf-Matchに付けてPUT/PATCH。変わっていれば412で「取り込み直して再送」を促す。
  • 削除の衝突回避: DELETEにIf-Matchを付け、最新でない場合は412。既に消えている場合は冪等性を保つため204を返す設計も多い。
  • 帯域節約の部分取得: Range + If-Rangeで中断したダウンロードを再開。タグ不一致なら通常の200で全体を返す。

返すステータスの目安

条件が一致したとき・しないときで返却コードが決まっています。

コード対応

状況 返すコード メモ
条件一致(GETのIf-None-Match/If-Modified-Since) 304 Not Modified ボディなし。ETag/Last-Modifiedは返す。
条件不一致(更新のIf-Match/If-Unmodified-Since) 412 Precondition Failed 再取得を促す。
条件ヘッダー必須なのに無い 428 Precondition Required If-Match必須のAPIポリシーで使う。

設計メモ

ETagは実装方法でぶれやすいので、発行ポリシーをチームで共有しておくと安全です。

運用のコツ

  • ETagは実体のハッシュやバージョン番号で生成し、部分更新でも確実に変わるようにする。
  • 弱いETag(W/)は意味上等価でもバイト列が違う場合に使う。強い比較が必要なら弱いタグを返さない。
  • If-MatchとIf-None-Matchを同時に解釈するときはIf-Match優先(RFC 9110 13.1.1)。
  • 304を返すときでもキャッシュ制御ヘッダー(Cache-Control, Expires, Vary)は忘れずに返す。

まとめ

条件付きリクエストは「無駄な転送の削減(304)」と「競合防止(412)」の二刀流。ETagを正しく付ければ、クライアントとサーバーの見通しがグッと良くなります。