HTTP キャッシュ
目次
先に要点
HTTP キャッシュでは、直接再利用できる期間の「鮮度」と、内容が変わったか確認する「検証」を分けます。応答の no-cache は保存を許可しますが、再利用前にオリジンでの検証の成功が必要です。no-store は保存を禁止します。面接と画像は 2016 年のものです。説明は 2026-10-15 に RFC 9111 と照合しましたが、ブラウザや CDN の新たな実測は行っていません。
午後、思いがけずAlibaba DingTalkのフロントエンドの面接を受けることになり、大変嬉しく思いました。 面接の内容は、これまでのプロジェクトから、Angular、React、Expressなどの使用技術に及び、自身の理解がまだ不十分であると感じさせられました。 例えば、私がReactが好きで、大規模プロジェクトではデータ処理のためにReduxと組み合わせて使う必要があると話すと、Reduxが具体的にどのような問題を解決するのかと問われ、明確に答えることができませんでした。しかし、全体的に見て、面接の前半部分はかなり手応えを感じていました。 しかし、最後に面接官から「HTTPプロトコルでキャッシュをどのように制御しますか?」という比較的基礎的な質問が出ました。 私は戸惑いました。普段の業務では全く触れる機会がなかったからです。自分の感覚で、ファイル名の変更やHTTPのheaderに適切なパラメータを設定することで制御するとしか言えませんでしたが、具体的にどのように設定するのか、設定する内容は何なのか、全く答えることができませんでした。きっとここで不採用になるだろうと感じました。しかし、諺にもある通り「人は同じ場所で二度転ぶべからず」です。この機会にHTTPキャッシュの関連内容を記録しておきたいと思います。
概要
HTTPでキャッシュを制御する主な方法は以下の通りです。
ExpiresCache-ControlLast-Modified/If-Modified-SinceEtag/If-None-Match
それでは、一つずつ見ていきましょう。
Expires
Expires は有効期限を絶対時刻で示し、現在の HTTP でも有効です。新鮮で再利用可能な応答は通常そのまま使えますが、再読み込みやリクエストの指示で通信が必要になることもあります。適用される Cache-Control の鮮度指示が優先されます。4 種のヘッダーを単一の優先順位に並べることはできません。過去の画像は現在のブログ設定を示すものでもありません。
上図は私のブログにあるjsファイルのHTTP headerです。赤枠1のExpiresが現在のDateから4時間後に設定されているのがわかります。つまり、通常このファイルはExpiresで指定された時間まではローカルキャッシュが直接使用され、サーバーから再取得されることはありません。
Cache-control
Cache-controlはExpiresと基本的に同じ役割で、現在のリソースの有効期間を示し、ブラウザがローカルキャッシュを使用するか、サーバーからリソースを再取得するかを制御します。しかし、異なる点は、Cache-controlの方がより詳細な制御が可能であることです。headerにExpiresが同時に存在する場合、Cache-controlの優先順位が高くなります。
次は応答の指示です。クライアントの要求を示すリクエスト側とは区別します。
| 指示 | 意味 |
|---|---|
| public | 他の指示に従う範囲で、本来制限され得る応答の共有保存を可能にする |
| private | 共有キャッシュへの保存を禁止。私有キャッシュは保存可能 |
| no-cache | 保存可能だが、再利用前にオリジンでの検証の成功が必要 |
| no-store | キャッシュへの保存を禁止 |
| max-age=N | 鮮度の寿命を N 秒に設定。年齢計算には Date、Age も関係する |
| s-maxage=N | 共有キャッシュの鮮度を設定し、その max-age や Expires より優先 |
| must-revalidate | 古くなった応答は検証に成功せず再利用してはならない |
リクエストの min-fresh は必要な残りの鮮度、max-stale は許容する期限超過を指定しますが、応答側の制限にも従います。
図のように、赤枠2の上のCache-Controlの欄の値はpublic, max-age=14400です。これは有効期間が14400s、つまり4時間であることを意味し、上記のExpiresの有効期限と同じです。しかし、ExpiresのようにMon, 07 Mar 2016 17:14:33 GMTといった具体的な時間を指定する必要はなく、最大寿命の時間を指定するだけで済みます。これは比較的よく使われる方法でしょう。
Last-Modified/If-Modified-Since
Last-Modified は表現の更新日時を示す検証子です。保存済み応答を検証するクライアントは If-Modified-Since を送れます。通常の GET/HEAD で、その日時から変更がなければサーバーは 304 を返して保存済み本文を再利用でき、変更があれば通常は 200 と新しい本文を返します。
Cache-Control との併用は必須ではなく、検証は max-age 超過時だけでもありません。no-cache、リクエスト指示、再読み込みなどでも必要になります。明示的な鮮度情報がない場合、HTTP の規則に従うヒューリスティックな鮮度を使うこともあります。
Etag/If-None-Match
ETag はサーバーが選定した表現に付ける検証タグで、必ずしもファイルハッシュではありません。クライアントは保存したタグを If-None-Match に入れられます。GET/HEAD では一致時に通常 304、不一致時に通常 200 と新しい本文を返します。max-age は前提条件ではありません。
画像引用元 図は鮮度判断と条件付き検証という別の段階を含んでいます。If-None-Match と If-Modified-Since が両方あれば、If-None-Match を先に評価します。ブラウザと CDN の共有キャッシュは別に考え、個人向け応答に public の例をそのまま使わないでください。Service Worker Cache API、アプリ独自のキャッシュ、履歴移動のキャッシュの全動作を説明するものでもありません。
参考文章: 1.ブラウザキャッシュメカニズム 2.ブラウザキャッシュ関連の HTTP ヘッダー紹介:Expires,Cache-Control,Last-Modified,ETag 3.HTTP のリクエストヘッダータグ If-Modified-Since