HTTP キャッシュとは何か#
なぜキャッシュが必要なのか?
私たちは、ウェブページにアクセスするたびにリソースを読み込む必要があることを知っています。これらのリソースがすべて遠くのサーバーから読み込まれると、リクエストとレスポンスの時間が非常に長くなります。もっと早くする方法はないのでしょうか?あります。近くの CDN からリソースをリクエストすることで、長距離のネットワーク転送を減らし、応答速度を自然に向上させることができます。しかし、CDN はクライアントから少し遠いので、ブラウザにデータをキャッシュさせて、外部にリソースをリクエストする回数をできるだけ減らす方法はないのでしょうか?実際、これらの考えはすべてキャッシュの考え方であり、各ノードでキャッシュを行うことで、遠くのリソースリクエストを大幅に減少させ、応答速度を向上させることができます。
キャッシュにはいくつかの概念があります
- Stale Content:古いコンテンツ、すなわちキャッシュされていて期限切れのコンテンツ
- Fresh Content:新鮮なコンテンツ、すなわちキャッシュされていて期限切れでないコンテンツ
- Cache Validation:キャッシュコンテンツの有効性を確認するためにサーバーに連絡し、期限切れが近づいたときに更新を受け取るプロセス。
- Cache Invalidation:キャッシュ内の古いコンテンツを削除するプロセス。
キャッシュは位置に応じてブラウザキャッシュ、プロキシキャッシュ、リバースプロキシキャッシュに分けられます。
ブラウザキャッシュ#
ブラウザキャッシュは特定のユーザーに限定されており、他のキャッシュとは異なり、プライベートなレスポンスを保存できます。
プロキシキャッシュ#
単一のユーザーにサービスを提供するブラウザキャッシュとは異なり、プロキシキャッシュは同じコンテンツにアクセスする数百の異なるユーザーにサービスを提供できます。通常、ISP によって実施されます。
リバースプロキシキャッシュ#
リバースプロキシキャッシュは通常サーバー側に配置され、サーバーの負荷を軽減するために使用されます。
キャッシュヘッダー#
サーバーがレスポンスを発行するたびに、キャッシュするかどうか、またその方法を指示するいくつかの HTTP ヘッダーが伴います。
Expires#
HTTP/1.1 とキャッシュ制御が導入される前に、Expires ヘッダーがあり、これはキャッシュが特定のコンテンツを新鮮と見なすべき時間を示すタイムスタンプです。例えば、
Expires: Mon, 13 Mar 2017 12:22:00 GMT
もちろん、キャッシュの時計はサーバーの時計と同期している必要があります。そうでないと、望ましい結果が得られない可能性があります。
Expires はまだ使用されていますが、現在はCache-Control
を優先すべきです。
Pragma#
これは HTTP/1.1 からの古い機能でもあり、ここやそこにPragma: no-cache
が使用されているのを見かけることがあります。これはレスポンスがキャッシュされるのを防ぐことを期待しています。
Cache-Control#
キャッシュ制御(Cache-Control)は、コンテンツがどのくらいの時間キャッシュされ、どのようにキャッシュされるべきかを規定します。
cache-control には複数の値を含めることができます。
private
キャッシュをプライベートに設定することは、そのコンテンツがどのプロキシでもキャッシュされないことを意味し、クライアント(すなわちブラウザ)だけがキャッシュします。
public
公開に設定されている場合、クライアントにキャッシュされるだけでなく、プロキシもキャッシュできます;多くの他のユーザーにサービスを提供します。
no-store
このコンテンツがどのキャッシュにもキャッシュされないことを指定します。
no-cache
no-cache はキャッシュを保持できることを示しますが、キャッシュされたコンテンツは提供される前にサーバーから再検証される必要があります(例えば、ETag を使用して)。つまり、サーバーへのリクエストは依然としてありますが、それは検証のためであり、キャッシュされたコンテンツをダウンロードするためではありません。
max-age: seconds
max-age は、コンテンツがキャッシュされる秒数を規定します。例えば、cache-control が以下のように見える場合。
cache-control: max-age=900, private
s-maxage: seconds
s-maxage の s - プレフィックスは共有を示します。この指令は共有キャッシュ専用です。max-age と同様に、キャッシュされる秒数を指定できます。存在する場合、共有キャッシュの max-age および expires ヘッダーを上書きします。
must-revalidate
must-revalidate が存在する場合、ネットワークの問題が発生し、コンテンツがサーバーから取得できない場合、ブラウザは検証なしで古いコンテンツを提供する可能性があります。must-revalidate はこの状況を回避できます。この指令が存在する場合、古いコンテンツを提供することはできず、データを提供する前にサーバーから再検証する必要があります。
proxy-revalidate
proxy-revalidate は must-revalidate に似ていますが、共有またはプロキシキャッシュに対する規定は同じです。言い換えれば、proxy-revalidate は must-revalidate に対して、s-maxage は max-age に対してのようなものです。
値の混合
これらの指令を異なる方法で組み合わせて異なるキャッシュ動作を実現できますが、no-cache/no-store
とpublic/private
は相互排他的です。no-store
とno-cache
の両方が指定された場合、no-store
がno-cache
に優先します。
バリデーター#
これまで、コンテンツがどのようにキャッシュされるか、どのくらいの時間キャッシュされたコンテンツが新鮮と見なされるかについて議論してきましたが、クライアントがサーバーからどのように検証を行うかについては議論していませんでした。以下では、この目的のために使用されるヘッダー情報について説明します。
Etag
Etag または "エンティティタグ" は HTTP/1.1 仕様で導入されました。Etag は単に一意の識別子であり、サーバーはこれをいくつかのリソースに関連付けます。この ETag は後にクライアントによって条件付き HTTP リクエストに使用され、「もし ETag が私の ETag と異なるなら、このリソースをください」と示します。ETag が一致しない場合のみコンテンツがダウンロードされます。
ETag を生成する方法は HTTP 文書に指定されておらず、通常は衝突を避けるためのハッシュ関数を使用してリソースの各バージョンに ETag を割り当てます。ETag には強いものと弱いものの 2 種類があります。
ETag: "j82j8232ha7sdh0q2882" - 強いETag
ETag: W/"j82j8232ha7sdh0q2882" - 弱いETag(`W/`でプレフィックス付き)
強い検証 ETag は、2 つのリソースが完全に同じであることを意味し、違いが全くありません。一方、弱い ETag は、2 つのリソースが厳密には異なるが、同じと見なすことができることを意味します。例えば、弱い ETag は動的コンテンツに対して非常に有用です。
今、Etag が何であるかはわかりましたが、ブラウザはどのようにこのリクエストを行うのでしょうか?サーバーにリクエストを送信し、同時にIf-None-Match
ヘッダーに利用可能な Etag を送信します。
次のような状況を考えてみてください:あなたはウェブページを開き、そこにフラグ画像が読み込まれ、キャッシュ期間は 60 秒、ETag はabc123xyz
です。約 30 分後、あなたはそのウェブページを再読み込みします。ブラウザは 60 秒内の新鮮なフラグが現在期限切れであることに気付きます;それはサーバーへのリクエストをトリガーし、if-none-match ヘッダーに期限切れのフラグ画像の ETag を送信します。
If-None-Match: "abc123xyz"
その後、サーバーはこの ETag とリソースの現在のバージョンの ETag を比較します。両方の ETag が一致する場合、サーバーは 304 未変更のレスポンスを返します。これはクライアントに対して、彼が持っているコピーがまだ良好であることを伝え、それは新しい 60 秒として見なされます。もし両方の ETag が一致しない場合、つまりフラグが変更されている可能性がある場合、クライアントには新しいフラグが送信され、彼が持っている古いフラグと置き換えられます。
Last-Modified
サーバーはLast-Modified
ヘッダーを含めることがあり、これはコンテンツが最後に変更された日付と時間を示します。
Last-Modified: Wed, 15 Mar 2017 12:30:26 GMT
コンテンツが期限切れになると、クライアントはサーバーに条件付きリクエストを送信し、If-Modified-Since
というヘッダーに最後の変更日を含めて、更新された Last-Modified 日付を取得します;もしそれがクライアントの日付と一致すれば、コンテンツのLast-Modified
日付は更新され、他の n 秒間新鮮と見なされます。もし受け取ったLast-Modified
日付がクライアントが持っている日付と一致しない場合、コンテンツはサーバーから再読み込みされ、クライアントが持っているコンテンツと置き換えられます。
If-Modified-Since: Wed, 15 Mar 2017 12:30:26 GMT
あなたは今、キャッシュされたコンテンツがLast-Modified
とETag
の両方を持っている場合はどうなるのかと疑問に思うかもしれません。その場合、両方を使用する必要があります。つまり、ETag が新しく取得したコンテンツと一致し、Last-Modified 日付も一致する場合にのみ、リソースは再ダウンロードされません。ETag が一致しないか、Last-Modified 日付がサーバー上の日付よりも大きい場合、コンテンツは再ダウンロードされる必要があります。