HTTP キャッシュ
==========

概要
--

HTTP キャッシュは、リクエストに関連付けられたレスポンスを格納し、格納されたレスポンスを後続のリクエストのために再利用します。

再利用にはいくつかの利点があります。まず、リクエストを元サーバーに届ける必要がないため、クライアントとキャッシュの距離が近いほど、レスポンスが速くなります。ほとんどの例は、ブラウザー自身がブラウザーのリクエストのキャッシュを格納するものです。

また、レスポンスが再利用可能な場合、元のサーバーはリクエストを処理する必要がありません。つまり、リクエストを解釈してルーティングしたり、クッキーに基づいてセッションを復元したり、結果をDBに問い合わせたり、テンプレートエンジンをレンダリングしたりする必要がありません。これによりサーバーの負荷が軽減されます。

キャッシュを適切に管理運営することは、システムを健全にするためにとても重要です。

キャッシュの種類
--------

HTTP Caching 仕様書では、キャッシュには大きく分けて 2 つの種類があります。**プライベートキャッシュ**と**共有キャッシュ**です。

### プライベートキャッシュ

プライベートキャッシュとは、特定のクライアント（通常はブラウザーキャッシュ）に結びついたキャッシュのことです。格納されるレスポンスは他のクライアントと共有されないので、プライベートキャッシュはそのユーザー用にパーソナライズされたレスポンスを格納することができます。

一方、プライベートキャッシュ以外のキャッシュにパーソナライズされたコンテンツが格納されると、他のユーザーがそのコンテンツを取得できる可能性があり、意図しない情報漏洩が発生する可能性があります。

レスポンスにパーソナライズされたコンテンツを含み、プライベートキャッシュにのみ格納したい場合は `private` ディレクティブを指定しなければなりません。


```http
Cache-Control: private
```


パーソナライズされたコンテンツは通常クッキーによって制御されますが、クッキーの存在が常にプライベートであることを示すとは限らないため、クッキーだけでレスポンスがプライベートになるわけではありません。

レスポンスに `Authorization` ヘッダーがある場合、プライベートキャッシュには格納されない（`public` を指定しない限り、共有キャッシュに格納される）ことに注意してください。

### 共有キャッシュ

共有キャッシュはクライアントとサーバーの間に格納され、ユーザー間で共有できるレスポンスを格納するために使用されます。共有キャッシュはさらに、**プロキシーキャッシュ** と **マネージドキャッシュ** に分類されます。

#### プロキシーキャッシュ

アクセス制御する機能に加えて、一部のプロキシーはネットワークからのトラフィックを削減するためにキャッシュを実装しています。これは通常サービス開発者によって管理されるものではないので、適切な HTTP ヘッダーなどによって 制御しなければなりません。しかし、過去には時代遅れのプロキシーキャッシュ実装、すなわち HTTP キャッシュ標準を正しく理解していない実装などが、開発者に問題を発生させることが多くありました。

以下のような**キッチンシンクヘッダー**を用いて、`no-store` のような現在の HTTP Caching 仕様書のディレクティブを理解しない、「古くて更新されていないプロキシーキャッシュ」の実装を回避しようします。


```http
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
```


しかし、近年 HTTPS が一般的になり、クライアント/サーバ間の通信が暗号化されるようになったため、 経路上のプロキシーキャッシュはレスポンスをトンネリングすることしかできず、 キャッシュとして振る舞うことができない場合が多くなっています。そのような場合は、レスポンスを見ることすらできないので、時代遅れのプロキシーキャッシュ実装を心配する必要はありません。

他にも、 TLS ブリッジプロキシーが、組織が管理する認証局 (CA) の資格情報を PC にインストールすることで、中間者 (person-in-the-middle) 的にすべての通信を復号し、アクセス制御などを行う場合、レスポンスの内容を見たり、キャッシュしたりすることが使用可能です。ただし、近年は CT (certificate transparency) が普及し、ブラウザーによっては SCT (signed certificate timestamp) で発行された証明書しか許可しないものもあるため、このメソッドでは企業ポリシーを適用する必要があります。このような制御された環境では、プロキシーキャッシュが「古くて更新されない」ことを心配する必要はありません。

#### マネージドキャッシュ

マネージドキャッシュは、サービス開発者が元サーバの負荷を分散し、コンテンツを効率的に開発するために明示的に展開するものです。例えば、リバースプロキシー、CDN、キャッシュ API と組み合わせたサービスワーカーなどがあります。

マネージドキャッシュの特性は、展開する製品によって異なります。ほとんどの場合、`Cache-Control` ヘッダーと自分自身で設定ファイルやダッシュボードを通してキャッシュの動作を制御することができます。

例えば、HTTP キャッシュ仕様書では、基本的にキャッシュを明示的に削除する方法は定義されていません。しかし、マネージドキャッシュでは、ダッシュボード操作や API 呼び出し、再起動などによって、格納されたレスポンスをいつでも削除することができます。これにより、より積極的なキャッシュ戦略が可能になります。

標準 HTTP キャッシュ仕様書のプロトコルを無視して、明示的な操作を行うことも使用可能です。例えば、次の例はプライベートキャッシュやプロキシキャッシュをオプトアウトし、自分自身で管理キャッシュにのみキャッシュする戦略を使用するように指定することができます。


```http
Cache-Control: no-store
```


例えば、Varnish Cache は VCL （Varnish Configuration Language、DSL の一種）ロジックを使用してキャッシュストレージを処理しますが、サービスワーカーとキャッシュ API との組み合わせにより、JavaScript でそのロジックを作成することができます。

つまり、マネージドキャッシュが意図的に `no-store` ディレクティブを無視したとしても、それを「標準に準拠していない」とみなす必要はないということです。キッチンシンクヘッダーの使用を避け、使用している管理キャッシュ機構のドキュメントを注意深く読み、使用する機構で指定された方法で適切にキャッシュを制御していることを確実に保持することです。

なお、CDNによっては、そのCDNに対してのみ有効なヘッダーを自分自身で提供しているものもあります（`Surrogate-Control` など）。現在、これらを標準化するために `CDN-Cache-Control` ヘッダーを定義する作業が進められています。

![キャッシュの種類](/ja/docs/Web/HTTP/Caching/type-of-cache.png)

ヒューリスティックキャッシュ
--------------

HTTP は可能な限りキャッシュするように設計されているので、`Cache-Control` が指定されていなくても、ある条件が満たされればレスポンスは格納されて再利用されます。これは**ヒューリスティックキャッシュ**と呼ばれます。

例えば、次のようなレスポンスを見てみましょう。このレスポンスの最終更新は 1 年前です。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT

<!doctype html>
…
```


1 年間更新されなかったコンテンツは、その後しばらく更新されないことがヒューリスティックに知られています。そのため、クライアントはこのレスポンスを（`max-age` がないにもかかわらず）格納し、しばらく再利用します。どのくらいの期間再利用するかは実装次第ですが、仕様では格納した後の時間の約 10 %（この場合は 0.1 年）を推奨しています。

ヒューリスティックキャッシュは `Cache-Control` 対応が広く採用される以前の回避策であり、基本的にすべてのレスポンスは `Cache-Control` ヘッダーを明示的に指定する必要があります。

age に基づく新鮮さと古さ
--------------

格納される HTTP レスポンスには、**新鮮** (fresh) と**古い** (stale) の 2 つの状態があります。新鮮な状態は通常、レスポンスがまだ有効で再利用できることを示し、古い状態はキャッシュされたレスポンスがすでに期限切れであることを意味しています。

レスポンスがいつ新鮮で、いつ古くなったかを判断する基準は **age** です。 HTTP では、 age はレスポンスが生成されてからの経過時間です。これは他にもキャッシュ機構における TTL に似ています。

次のレスポンス例を見てみましょう（604800 秒は 1 週間）。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800

<!doctype html>
…
```


この例のレスポンスが格納されたキャッシュは、レスポンスが生成されてからの経過時刻を計算し、その結果をレスポンスの *age* として使用します。

例えば、レスポンスの `max-age` の意味は以下の通りです。

* レスポンスの age が 1 週間未満である場合、そのレスポンスは新鮮です。
* レスポンスの age が 1 週間を超える場合、そのレスポンスは古いです。

格納されたレスポンスが新鮮である限り、クライアントのリクエストを履行するために使用されます。

レスポンスが共有キャッシュに格納されるとき、クライアントにレスポンスの age を通知する必要があります。例えば、共有キャッシュがレスポンスを 1 日間格納された場合は、次の例のようになります。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800
Age: 86400

<!doctype html>
…
```


そのレスポンスを受け取ったクライアントは、レスポンスの `max-age` と `Age` の差である残り 518400 秒の間、そのレスポンスが新鮮であることを受け取ります。

有効期限または max-age
---------------

HTTP/1.0 では、鮮度は `Expires` ヘッダーを使用して指定 していました。

`Expires` ヘッダーは、経過時間を指定するのではなく、明示的な時刻を使用してキャッシュの有効期間を指定します。


```http
Expires: Tue, 28 Feb 2022 22:22:22 GMT
```


しかし、この時刻形式は解釈が難しく、多くの実装バグが見つかり、システムクロックを意図的にずらすことで問題を誘発する可能性がありました。そのため、HTTP/1.1 では `Cache-Control` に経過時間を指定する `max-age` が採用されました。

`Expires` と `Cache-Control: max-age` の両方が利用できる場合は、 `max-age` を優先するように定義されています。そのため、HTTP/1.1 が広く使用されるようになった現在では `Expires` を提供する必要はありません。

Vary
----

レスポンスを判別する方法は、基本的に URL に基づいています。

![keyed with url](/ja/docs/Web/HTTP/Caching/keyed-with-url.png)

しかし、同じ URL を持っていてもレスポンスのコンテンツが常に同じとは限りません。特にコンテンツネゴシエーションが行われた場合、サーバからのレスポンスは `Accept`、`Accept-Language`、`Accept-Encoding` リクエストヘッダーの値によって変わる可能性があります。

例えば、`Accept-Language: en` ヘッダーで返されてキャッシュされた英語のコンテンツに対して、`Accept-Language: ja` リクエストヘッダーがあるリクエストに対してキャッシュされたレスポンスを再利用することは望ましくありません。この場合、"`Accept-Language`" を `Vary` ヘッダーの値に追加することで、レスポンスが言語に基づいて別個にキャッシュされるようにすることができます。


```http
Vary: Accept-Language
```


これにより、キャッシュはレスポンス URL と `Accept-Language` リクエストヘッダーの合成に基づいて生成されます。

![URL と言語がキーになる例](/ja/docs/Web/HTTP/Caching/keyed-with-url-and-language.png)

また、（例えばレスポンシブデザインのために）ユーザーエージェントに基づいてコンテンツの最適化を提供している場合、 `Vary` ヘッダーの値に "`User-Agent`" を含めることができます。しかし、一般的に `User-Agent` リクエストヘッダには非常に多くのバリエーションがあり、キャッシュが再利用される可能性を大幅に縮小します。そのため、可能であれば `User-Agent` リクエストヘッダーに基づくのではなく、機能検出に基づいて動作を変化させる方法を検討してください。

キャッシュされたパーソナライズされたコンテンツが他に再利用されるのを防ぐために、クッキーを使用するアプリケーションでは、`Vary`にクッキーを指定する代わりに `Cache-Control: private` を指定する必要があります。

検証
--

古くなったレスポンスはすぐには破棄されません。HTTPには、元のサーバーに問い合わせることで、古いレスポンスを新しいものに変換する仕組みがあります。これは**検証**、または**再認証**と呼ばれます。

検証は `If-Modified-Since` または `If-None-Match` リクエストヘッダーを含む**条件付きリクエスト**を使用することで行われます。

### If-Modified-Since

以下のレスポンスは 22:22:22 に生成され、 `max-age` は 1 時間なので、 23:22:22 まで新鮮であることがわかります。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600

<!doctype html>
…
```


23:22:22 になると、レスポンスは古くなり、キャッシュを再利用できなくなります。そのため、下記のリクエストはクライアントが `If-Modified-Since` リクエストヘッダーを持つリクエストを送信し、指定した時刻以降に変更があったかどうかをサーバーに問い合わせることを示しています。


```http
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT
```


コンテンツが指定した時刻から変更されていない場合、サーバーは `304 Not Modified` というレスポンスを返します。

このレスポンスは「変更されていない」ことを示すだけなので、レスポンス本体はありません。ステータスコードだけですので、転送サイズは非常に小さくなります。


```http
HTTP/1.1 304 Not Modified
Content-Type: text/html
Date: Tue, 22 Feb 2022 23:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600
```


そのレスポンスを受信すると、クライアントは格納される古いレスポンスを新しいものに戻し、残りの 1 時間の間に再利用することができます。

サーバーはオペレーティングシステムのファイルシステムから更新時刻を取得することができ、静的ファイルを提供する場合には相対的に容易です。しかし、いくつかの問題があります。例えば、時刻形式が複雑で解釈しにくい、分散サーバーではファイル更新時刻の同期が取りにくいなどです。

このような問題を解決するために、 `ETag` レスポンスヘッダーが標準化されました。

### ETag/If-None-Match

`ETag` レスポンスヘッダーの値は、サーバーが生成する任意の値です。サーバーがどのように値を生成しなければならないかという制限はないので、サーバーはどのような意味に基づいて値を設定しても自由です。例えば、本体のコンテンツのハッシュやバージョン番号などです。

例えば、 `ETag` ヘッダーにハッシュ値を用い、 `index.html` リソースのハッシュ値が `33a64df5` の場合、レスポンスは以下のようになります。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
ETag: "33a64df5"
Cache-Control: max-age=3600

<!doctype html>
…
```


そのレスポンスが古かった場合、クライアントはキャッシュされたレスポンスの `ETag` レスポンスヘッダーの値を受け取り、それを `If-None-Match` リクエストヘッダーに入れ、リソースが変更されたかどうかをサーバーに問い合わせます。


```http
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-None-Match: "33a64df5"
```


リクエストされたリソースの `ETag` ヘッダーの値がリクエストの `If-None-Match` の値と同じ場合、サーバーは `304 Not Modified` を返します。

しかし、リクエストされたリソースがこれで異なる `ETag` 値を持つようになったとサーバが判断した場合、サーバは代わりに `200 OK` とリソースの最新バージョンで応答します。

**メモ:** RFC9110 では、できれば `200` レスポンスに対して `ETag` と `Last-Modified` の両方を送信することを推奨しています。
 キャッシュの再検証の際に、 `If-Modified-Since` と `If-None-Match` の両方が存在する場合は、 `If-None-Match` を優先します。
 キャッシュだけを考えているのであれば、 `Last-Modified` は不要だと思うかもしれません。
 しかし、 `Last-Modified` はキャッシュに有益なだけではなく、コンテンツ管理 (CMS) システムが最終更新時刻を表示したり、クローラーがクロール頻度を調整したり、他にも様々な目的で使用する標準 HTTP ヘッダーです。
 そのため、 HTTP エコシステム全体を考慮すると、 `ETag` と `Last-Modified` の両方を提供した方が良いでしょう。


### 強制的な再検証

レスポンスを再利用せず、常に最新のコンテンツをサーバーから取得したい場合は、 `no-cache` ディレクティブを使用して強制的に検証することができます。

レスポンスに `Cache-Control: no-cache` を `Last-Modified` と `ETag` とともに追加することで、下記で示すように、リクエストされたリソースが更新されていればクライアントは `200 OK` レスポンスを受け取り、更新されていなければ `304 Not Modified` レスポンスを受け取ります。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
ETag: deadbeef
Cache-Control: no-cache

<!doctype html>
…
```


`max-age=0`と `must-revalidate` の組み合わせは `no-cache` と同じ意味を持つとよく言われています。


```http
Cache-Control: max-age=0, must-revalidate
```


`max-age=0` はレスポンスが即座に古くなることを意味しており、 `must-revalidate` は古くなったレスポンスを再検証せずに再利用してはいけないことを意味しています。つまり、この組み合わせの意味は `no-cache` と同じです。

しかし、この `max-age=0` を使用することは、HTTP/1.1 より前の実装の多くが `no-cache` ディレクティブを処理することができなかったことの名残です。そのため、その制限に対処するために、回避策として `max-age=0` を使用していました。

しかし、これで HTTP/1.1 準拠のサーバが広く普及したため、 `max-age=0` と `must-revalidate` の組み合わせを使用する理由はなくなりました。

キャッシュしない
--------

`no-cache` ディレクティブはレスポンスを格納しないようにするのではなく、再検証せずにレスポンスを再利用しないようにします。

レスポンスをキャッシュに格納したくない場合は、 `no-store` を使用してください。


```http
Cache-Control: no-store
```


しかし、一般的に、「キャッシュしない」という要件は、実際には以下のような設定になります。

* プライバシー保護のため、特定のクライアント以外にレスポンスを格納されたくない場合。
* 常に最新の情報を提供したい場合。
* 時代遅れの実装であり、何が起こるか分からない場合。

このような状況では、 `no-store` は常に適切なディレクティブとは限りません。

次の節では、その状況をより詳しく見ていきます。

### 他の人と共有しないようにする

パーソナライズされたコンテンツを含むレスポンスが、予期せずキャッシュの他のユーザーに見えてしまうのは問題です。

このような場合、`private` ディレクティブを使用することで、パーソナライズされたレスポンスは特定のクライアントにのみ格納され、他のキャッシュユーザーに漏れることはなくなります。


```http
Cache-Control: private
```


このような場合、 `no-store` を指定したとしても、 `private` も指定しなければなりません。

### 常に最新のコンテンツを提供する

`no-store` ディレクティブはレスポンスが格納されることを防ぎますが、同じ URL に対して既に格納されているレスポンスを削除することはありません。

言い換えれば、特定の URL に対して古いレスポンスが既に格納されている場合、 `no-store` を返しても古いレスポンスが再利用されることはありません。

しかし、`no-cache` ディレクティブは格納されたレスポンスを再利用するために、 クライアントに検証リクエストを送らせます。


```http
Cache-Control: no-cache
```


サーバーが条件付きリクエストに対応していない場合は、クライアントに毎回サーバーにアクセスするように強制し、常に `200 OK` で最新のレスポンスを取得することができます。

### 古い実装への対応

`no-store` を無視する古い実装を回避するために、以下のようなキッチンシンクのようなヘッダーが使用されているかもしれません。


```http
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
```


推奨 されている方法は、このような古い実装に対処するために代替手段として `no-cache` を使用することです。また、`no-cache` が最初から指定されていても、サーバーは常にリクエストを受け取るので問題ありません。

もし気になるのが共有キャッシュであれば、 `private` も追加することで、意図しないキャッシュを防ぐことができます。


```http
Cache-Control: no-cache, private
```

### `no-store` で失われるもの

`no-store` を追加することは、キャッシュをオプトアウトするために正しい方法だと思うかもしれません。

しかし、`no-store` を自由に許可することは推奨されません。ブラウザーのバック/フォワードキャッシュを含めるために、 HTTP とブラウザーが持つ多くの利点を失ってしまうからです。

したがって、ウェブプラットフォームの全機能の利点を享受するには、 `no-cache` を `private` と組み合わせて使用することを推奨します。

再読み込みと強制再読み込み
-------------

検証はレスポンスだけでなく、リクエストに対しても行うことができます。

**再読み込み** (reload) と**強制再読み込み** (force reload) は、ブラウザー側から実行される検証の一般的な例です。

### 再読み込み

ウィンドウの破損からの回復やリソースの最新バージョンへの更新のために、ブラウザーはユーザーに再読み込み機能を提供しています。

ブラウザーの再読み込み中に送信される HTTP リクエストを簡略化すると、以下のようになります。


```http
GET / HTTP/1.1
Host: example.com
Cache-Control: max-age=0
If-None-Match: "deadbeef"
If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT
```


（Chrome、Edge、Firefox からのリクエストは上記のものにとてもよく似ています。 Safari からのリクエストは多少異なります。）

リクエストの `max-age=0` ディレクティブは、「age が 0 以下のレスポンスの再利用」を指定します。つまり、事実上、中間で格納されたレスポンスは再利用されません。

その結果、リクエストは `If-None-Match` と `If-Modified-Since` によって検証されます。

この動作はフェッチ標準でも定義されており、JavaScript でキャッシュモードを `no-cache` に設定して `fetch()` を呼び出すことで再現することができます（この場合 `reload` は正しいモードではないことに注意してください）。


```js
// メモ: "reload" は通常の再読み込みには正しいモードではありません。 "no-cache" です。
fetch("/", { cache: "no-cache" });
```

### 強制再読み込み

ブラウザーがリロード時に `max-age=0` を使用するのは、 HTTP/1.1 より前の古い実装の多くが `no-cache` を理解していなかったためです。しかし、 `no-cache` はこの用途ではこれで問題なく、**強制再読み込み**はキャッシュされたレスポンスをバイパスするための追加の方法です。

ブラウザーが **強制再読み込み**しているときの HTTP リクエストは以下のようになります。


```http
GET / HTTP/1.1
Host: example.com
Pragma: no-cache
Cache-Control: no-cache
```


（Chrome、Edge、Firefox からのリクエストは上記のものにとてもよく似ています。 Safari からのリクエストは多少異なります。）

これは `no-cache` による条件付きリクエストではないので、元のサーバーから `200 OK` を取得することは確実です。

この動作はフェッチ標準でも定義されており、JavaScript でキャッシュモードを `reload` にして `fetch()` を呼び出すことで再現できます（`force-reload` ではないことに注意してください）。


```js
// メモ: "reload" （"no-cache" ではない）は「強制再読み込み」で正しいモードです。
fetch("/", { cache: "reload" });
```

### 再検証の回避

変更されることのないコンテンツには、キャッシュバスティング、つまり、リクエスト URL にバージョン番号やハッシュ値などを含めることにより、長い `max-age` を指定してください。

しかし、ユーザーが再読み込みすると、たとえサーバーがそのコンテンツが不変であることを知っていたとしても、再検証リクエストが送られます。

これを防ぐために、`immutable` ディレクティブを使用して、コンテンツは変更されないので再検証は不要であることを明示することができます。


```http
Cache-Control: max-age=31536000, immutable
```


これにより、再読み込み時の不要な再検証を防ぐことができます。

このディレクティブを実装する代わりに、 Chrome は実装を変更し、サブリソースの再読み込み中に再検証を行わないようにしました。

格納されたレスポンスの削除
-------------

長い `max-age` で格納されているレスポンスを削除する方法は基本的にありません。

例えば、以下のように `https://example.com/` からのレスポンスが格納されていたとします。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: max-age=31536000

<!doctype html>
…
```


サーバー上で有効期限が切れたレスポンスを上書きしたいと思うかもしれませんが、レスポンスが格納されると、キャッシュによりそれ以上のリクエストがサーバーに到達しないため、サーバー側でできることはまだありません。

詳細仕様で言及されているメソッドのひとつに、 `POST` のような安全でないメソッドで同じ URL のリクエストを送るというものがありますが、通常多くのクライアントにとって意図的に行うことは困難です。

`Clear-Site-Data:cache` ヘッダーと値の仕様もありますが、すべてのブラウザーが対応しているわけではありません。また、使用されている場合でも、ブラウザーのキャッシュにのみ影響し、中間キャッシュには影響を及ぼしません。

したがって、格納されるレスポンスはユーザーが手動で再読み込み、強制再読み込み、履歴クリアの操作を行わない限り、その `max-age` 期間は残るものとみなされます。

キャッシュはサーバーへのアクセスを削減し、サーバーがその URL の制御を失うことを意味しています。サーバーが URL の制御を失いたくない場合、例えばリソースが頻繁に更新されるような場合、 `no-cache` を追加して、サーバが常にリクエストを受け取り、意図するレスポンスを送信できるようにする必要があります。

リクエストの折りたたみ
-----------

共有キャッシュは主に元のサーバーの前にあり、元のサーバーへのトラフィックを削減することを意図しています。

したがって、複数の同じリクエストが同時に共有キャッシュに到着した場合、 中間キャッシュはそれ自身に代わって単一のリクエストをオリジンに転送し、 その結果をすべてのクライアントに再利用することがあります。これは**リクエストの折りたたみ**と呼ばれます。

リクエストの折りたたみはリクエストが同時に到着したときに起こるので、レスポンスに `max-age=0` や `no-cache` が指定されたとしても、再利用されます。

レスポンスが具体的なユーザーにパーソナライズされていて、折りたたみ時に共有されたくない場合は `private` ディレクティブを追加してください。

![リクエストの折りたたみ](/ja/docs/Web/HTTP/Caching/request-collapse.png)

良くあるキャッシュパターン
-------------

`Cache-Control` の仕様には多くのディレクティブがあり、すべてを理解するのは難しいかもしれません。しかし、ほとんどのウェブサイトは一握りのパターンの組み合わせでカバーすることができます。

この章では、キャッシュを設計する際の一般的なパターンを記述します。

### 既定の設定

上記のように、キャッシュの既定値（つまり `Cache-Control` のないレスポンス）は単純に「キャッシュしない」ではなく、いわゆる「ヒューリスティックキャッシュ」に従った暗黙的なキャッシュです。

ヒューリスティックキャッシュを避けるためには、すべてのレスポンスに既定値の `Cache-Control` ヘッダーを明示的に付与することが推奨されます。

既定値では常に最新バージョンのリソースを転送するように保持するために、既定値の `Cache-Control` に `no-cache` を含めるのが一般的です。


```http
Cache-Control: no-cache
```


また、サービスワーカースクリプトが Cookie や他のログインメソッドを実装しており、コンテンツがユーザーごとにパーソナライズされている場合は、他のユーザーと共有されないように `private` も指定されなければなりません。


```http
Cache-Control: no-cache, private
```

### キャッシュの破棄

キャッシュと最もうまく動作するリソースは、コンテンツが変更されることのない静的で不変なファイルです。そして、変更されるリソースについては、コンテンツが変更されるたびに URL を変更し、 URL 単位を詳しくキャッシュできるようにするのが一般的なベストプラクティスです。

例えば、次のような HTML を考えてみましょう。


```html
<script src="bundle.js"></script>
<link rel="stylesheet" href="build.css" />
<body>
  hello
</body>
```


現代のウェブ開発では、開発の進捗に応じて JavaScript や CSS のリソースが頻繁に更新されます。また、クライアントが使用する JavaScript や CSS リソースのバージョンがずれていると、表示が崩れてしまいます。

そのため、上記の HTML では `bundle.js` と `build.css` を `max-age` でキャッシュすることが難しくなっています。

従って、 JavaScript と CSS をバージョン番号やハッシュ値に基づいて変更する部分を含めた URL で提供することができます。その方法のいくつかを下記に示します。
```

# ファイル名にバージョンを入れる
bundle.v123.js

# クエリーにバージョンを入れる
bundle.js?v=123

# ファイル名にハッシュを入れる
bundle.YsAIAAAA-QG4G6kCMAMBAAAAAAAoK.js

# クエリーにハッシュを入れる
bundle.js?v=YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
```


キャッシュは URL に基づいてリソースを判別するので、リソースが更新されたときに URL が変更されると、キャッシュは再利用されません。


```html
<script src="bundle.v123.js"></script>
<link rel="stylesheet" href="build.v123.css" />
<body>
  hello
</body>
```


その設計であれば、 JavaScript も CSS リソースも長時間キャッシュすることができます。では、 `max-age` はどれくらいの長さに設定するべきなのでしょうか？ QPACK 仕様がその答えを提供しています。

QPACK は HTTP ヘッダーフィールドを圧縮するための標準であり、一般的に使用するフィールド値の表が定義されています。

一般的に使用するキャッシュヘッダーの値を下記に示します。
```

36 cache-control max-age=0
37 cache-control max-age=604800
38 cache-control max-age=2592000
39 cache-control no-cache
40 cache-control no-store
41 cache-control public, max-age=31536000
```


これらの番号付きオプションのいずれかを選択すると、 HTTP3 で転送するときに値を 1 バイトに圧縮することができます。

番号 `37`, `38`, `41` は 1 週間、 1 ヶ月、 1 年の期間です。

キャッシュは新しい項目が保存されると古い項目を除去するので、 `max-age` が 1 週間に設定されていたとしても、 1 週間後に格納されるレスポンスが存在する確率はそれほど高くありません。したがって、実際にはどちらを選んでもあまり違いはありません。

`41` 番が最も長い `max-age` （1 年）を持っていますが、 `public` が付いていることに注意してください。

`public` 値は `Authorization` ヘッダーを表示している場合でも、レスポンスを保存できるようにする効果があります。

**メモ:** `public` ディレクティブは `Authorization` ヘッダーが設定されたときにレスポンスを格納する必要がある場合にのみ使用してください。
 `max-age` が指定されている限り、レスポンスは共有キャッシュに格納されるからです。


そのため、レスポンスが基本的な認証でパーソナライズされている場合、 `public` があると問題が発生する可能性があります。もしそれが心配なら、 2 つ目の値である `38` （1 ヶ月）を選ぶことができます。


```http
# bundle.v123.js へのレスポンス

# 認証によりパーソナライズされることがない場合
Cache-Control: public, max-age=31536000

# 確信が持てない場合
Cache-Control: max-age=2592000
```

### 検証

再読み込み時にリソースを再送信する必要がないように、 `Last-Modified` ヘッダーと `ETag` ヘッダーを設定することを忘れないでください。ビルド済みの静的ファイルに対してこれらのヘッダーを生成するのは簡単です。

ここでの `ETag` 値はファイルのハッシュです。


```http
# bundle.v123.js へのレスポンス
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
```


さらに、 `immutable` を追加することで、再読み込み時の検証を防ぐことができます。

組み合わせた結果を下記に示します。


```http
# bundle.v123.js
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 1024
Cache-Control: public, max-age=31536000, immutable
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
```


**キャッシュ破棄**は、コンテンツが変更されたときに URL を変更することで、長期間にわたってレスポンスをキャッシュ可能にするテクニックです。このテクニックは、画像などのすべてのサブリソースに適用できます。

**メモ:** `immutable` と QPACK を使用することを評価する場合：
 もし `immutable` が QPACK が提供する定義済みの値を変更することを懸念しているのであれば、 `Cache-Control` 値を 2 行に分割することで、`immutable` の部分を別個の文字にエンコードすることができると考えることができます。ただし、これは特定の QPACK 実装が用いるエンコーディングアルゴリズムに依存します。



```http
Cache-Control: public, max-age=31536000
Cache-Control: immutable
```

### 主要リソース

メインリソースはサブリソースと異なり、URLの装飾ができないため、キャッシュ破棄ができません。

次の HTML そのものが格納されている場合、サーバー側でコンテンツが更新されても最新版を表示することはできません。


```html
<script src="bundle.v123.js"></script>
<link rel="stylesheet" href="build.v123.css" />
<body>
  hello
</body>
```


その場合、 HTML を格納するためではなく、常に最新の状態にしたいだけなので、 `no-store` ではなく、 `no-cache` が適切でしょう。

さらに、 `Last-Modified` と `ETag` を追加することで、クライアントは条件付きのリクエストを送ることができるようになり、HTML に更新がない場合は `304 Not Modified` を返すことができるようになります。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: no-cache
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: AAPuIbAOdvAGEETbgAAAAAAABAAE
```


この設定はパーソナライズされていない HTML には適切ですが、例えばログイン後など、クッキーを使用してパーソナライズされるレスポンスには `private` を指定することを忘れないでください。


```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: no-cache, private
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: AAPuIbAOdvAGEETbgAAAAAAABAAE
Set-Cookie: \_\_Host-SID=AHNtAyt3fvJrUL5g5tnGwER; Secure; Path=/; HttpOnly
```


同じことは、`favicon.ico`、`manifest.json`、`.well-known`、キャッシュ破棄を使用して URL を変更できない API エンドポイントにも使用することができます。

ほとんどのウェブコンテンツは、前述した 2 つのパターンの組み合わせに応じた対応が可能です。

### マネージドキャッシュの詳細

前節で説明したメソッドでは、キャッシュ破棄を使用することでサブリソースを長時間キャッシュすることができますが、メインリソース（通常は HTML 文書）はキャッシュすることができません。

メインリソースのキャッシュが難しいのは、HTTP キャッシュ仕様の標準ディレクティブを使用するだけでは、サーバーでコンテンツが更新されたときにキャッシュの内容を積極的に削除する方法がないからです。

しかし、CDN やサービスワーカーなどのマネージドキャッシュを展開することで使用可能です。

例えば、API やダッシュボードの処理によってキャッシュの破棄が可能な CDN では、メインのリソースを格納し、サーバーで更新が発生した場合にのみ関連するキャッシュを明示的に破棄することで、より積極的なキャッシュ戦略が可能になります。

サービスワーカーは、サーバで更新が発生したときにキャッシュ API のコンテンツを削除できれば、同じことができます。

より詳しい情報は、 CDN のドキュメント化や サービスワーカーのドキュメント を参照してください。

関連情報
----

* RFC 9111: Hypertext Transfer Protocol (HTTP/1.1): Caching
* Caching Tutorial - Mark Nottingham