12月24日、Dan Cătălin Burzo氏が「HTTP caching, a refresher · Dan Cătălin Burzo」と題したブログ記事を公開した。この記事では、HTTPキャッシュの最新仕様である RFC 9111 を踏まえ、Cache-Control ヘッダーを中心に HTTP キャッシュの仕組みと各種ディレクティブの意味、ブラウザや中間キャッシュの挙動について詳しく紹介されている。以下に、その内容を紹介する。
本記事は、2022年に更新された HTTP キャッシュ標準 RFC 9111 を読み解く形で構成されている。 Cache-Control ヘッダーは、 ブラウザのローカルキャッシュだけでなく、プロキシや CDN といった中間キャッシュも含め、HTTP レスポンスをどのように保存・再利用すべきかを指示するための仕組みである。


Cache-Control ヘッダーはカンマ区切りのディレクティブ集合を受け付け、リクエスト用のものとレスポンス用のものが存在する。典型的なレスポンス例は以下の通りである。
HTTP/2 200
Cache-Control: max-age=0, must-revalidate
キャッシュの「新鮮さ」とは何か
キャッシュがリクエストを受け取ると、保存されているレスポンスがまだ「新鮮(fresh)」で再利用可能か、それとも「期限切れ(stale)」で再検証が必要かを判断する必要がある。
キャッシュの期限は、以下のような優先順位で決定される。
Cache-Control: max-age=<秒数>が指定されている場合、その値- それがない場合、
ExpiresヘッダーとDateヘッダーの差分 Expiresも存在しない場合、Last-Modifiedを用いたヒューリスティックな判断が行われる可能性がある
期限切れレスポンスと再検証
レスポンスが期限切れ(stale)になったからといって、即座に破棄されるわけではない。キャッシュはオリジンサーバーに対して「条件付きリクエスト」を送信し、再検証を行う。これにより、内容が変わっていなければデータ転送を伴わずに済み、通常のリクエストより高速になる。
再検証では以下のような挙動が行われる。
- 条件が満たされた場合、サーバーは
200 OKと新しいレスポンスボディを返す - 変更がない場合、
304 Not Modifiedを返し、既存のキャッシュを再利用可能とする
この条件付けには、従来の Last-Modified ヘッダーや、より柔軟な識別子である ETag ヘッダーが用いられる。ETag は、更新時刻やファイルサイズ、内容などを元に生成される一意な文字列である。
Cache-Control レスポンスディレクティブの整理
主要なレスポンスディレクティブを以下にまとめた。
max-age=<number>
レスポンスが新鮮とみなされる秒数を定義する。must-revalidate
stale なレスポンスを再検証なしで再利用してはならないことを示す。サーバーエラー時にはエラーを返す必要がある。no-cache
すべてのレスポンスについて、再利用前に必ず再検証を要求する。実質的にはmax-age=0, must-revalidateに近い挙動となる。no-store
リクエスト・レスポンスを一切保存してはならないことを示す。ただし完全なプライバシー保証ではない点が明示されている。must-understand
キャッシュが意味を理解できないステータスコードのレスポンスを保存・再利用しないよう指示する。private
単一ユーザー向けのレスポンスであり、共有キャッシュには保存させない。public
通常は禁止される認証付きレスポンスであっても、共有キャッシュでの保存を許可する。s-maxage=<number>max-ageと同様だが、共有キャッシュのみに適用される。proxy-revalidate
共有キャッシュ版のmust-revalidateに相当する。no-transform
中間者による画像圧縮などの変換を禁止する。stale-while-revalidate=<number>
一定時間以内の stale レスポンスを返しつつ、裏で再検証を行うことを許可する。stale-if-error=<number>
再検証時にエラーが発生した場合、一定時間内であれば stale レスポンスの使用を許可する。
Cache-Control リクエストディレクティブ
Cache-Control はレスポンスだけでなくリクエストにも指定できる。これはクライアント側の鮮度に対する希望を示すもので、キャッシュはレスポンス側の指定と突き合わせて判断する。
max-age=<number>
指定秒数以内に生成されたレスポンスを要求する。max-stale[=<number>]
一定時間以内、または無制限に stale レスポンスを許容する。min-fresh=<number>
指定秒数以上の鮮度が残っているレスポンスを希望する。no-cache
再検証なしの利用を避けたいことを示す。no-store
リクエストおよびレスポンスを保存しないよう要求する。no-transform
内容変換を避けるよう要求する。only-if-cached
キャッシュ済みレスポンスのみを要求し、なければ 504 エラーとする。
ブラウザのリロード挙動
記事では、ブラウザの「ソフトリロード」と「ハードリロード」の違いも解説されている。
ソフトリロード
HTML 本体のみ再検証し、サブリソースは通常のキャッシュ規則に従う。Chrome はCache-Control: max-age=0を付与する点が特徴である。ハードリロード
HTML とサブリソースすべてに対しCache-Control: no-cacheを付与し、キャッシュを事実上無視する。
immutable ディレクティブの位置付け
immutable は、鮮度期間中はリソースが変化しないことを示し、リロード時の無駄な再検証を防ぐために導入された。しかし現在のブラウザは、サブリソースを再検証しないリロード戦略を採用しており、このディレクティブの価値はやや曖昧な立場にあると指摘されている。
認証付きリクエストのキャッシュ
Authorization ヘッダーを含むリクエストのレスポンスは、原則として共有キャッシュに保存できない。ただし以下のディレクティブが明示されている場合は例外となる。
publics-maxage=<number>must-revalidate
一方で private が指定されている場合、これらの例外は無効化される。
まとめ
本記事は、HTTP キャッシュに関する主要な概念とディレクティブの関係性を整理し、仕様と実装の両面から理解することを目的としている。過去のガイドが、仕様解釈だけでなく非準拠実装への対策も含んでいた点に触れつつ、近年のブラウザやキャッシュの改善によって、どの対策が不要になりつつあるのかを再評価する余地があると結んでいる。
詳細はHTTP caching, a refresher · Dan Cătălin Burzoを参照していただきたい。