はじめに
「キャッシュが残ってて最新のデータが見えない」ことや「ユーザごとに違うはずのページが他の人に見えてしまった」など,キャッシュによる意図しない挙動に頭を悩ませた経験があったため簡単にまとめてみました.
この記事では,IIJ Engineers Blogの解説記事をベースに,HTTPのキャッシュ制御とプロキシの役割について整理しています.こちらの記事は図などもおおく非常にわかりやすいのでおすすめです!(その分ボリュームがあるため,短いバージョンを本記事で作成します)
用語の整理
用語 | 説明 |
---|---|
ブラウザ | Webページを見るためのアプリケーション |
Webサーバ | HTMLなどのコンテンツを提供するサーバ |
HTTPクライアント | Webサーバにアクセスする側.今回はブラウザ |
プロキシ | 通信を中継・代理するサーバ.通信の制御やキャッシュで使用 |
フォワードプロキシ | クライアントの代理.社内LAN → インターネットなど.内→外 |
リバースプロキシ | サーバの代理.CDNやロードバランサが該当.外→内 |
キャッシュの基本
現代のWebページはリッチ化の一途をたどっており,通信速度が高速になったとしてもリソースを逐一ダウンロードしていては時間がかかってしまいます.それを防ぐために,すでにダウンロード済みのものを利用するという高速化技術をキャッシュと言います.これはブラウザやプロキシ等で行われます.
さらにキャッシュにリソースを保持しておく時間的な長さのことをキャッシュ期間といいます.
ここで,キャッシュに存在するトレードオフを紹介します.
キャッシュ期間を長くすると...
- ✅ 重複したリソースのダウンロードやWebサーバとの通信量が減り,表示が速くなる
- ❌ 古いデータが残ってしまう可能性がある
キャッシュ期間を短くすると...
- ✅ 常に最新の情報を取得できる
- ❌ 通信が増えたり,キャッシュの意味がなくなる
理想としては,リソースが更新された時だけダウンロードを行い,キャッシュを常に新しい状態に保つことですよね.
これを実現するために行われるのが「キャッシュの検証」です.
キャッシュの検証と 304 Not Modified
キャッシュの検証は,ブラウザがサーバに「このキャッシュ,まだ使っていい?」と確認する仕組みです.
サーバの返事が「変更なし」なら...
- ステータスコード
304 Not Modified
が返る - ブラウザはキャッシュをそのまま再利用
- 同じデータを再度ダウンロードせず,効率的!
304応答の仕組み:2つの方式
1. 更新時間での検証:If-Modified-Since
ヘッダ
- サーバ側:
Last-Modified
ヘッダで最終更新日時を返す - ブラウザ側: 次回リクエスト時の
If-Modified-Since
に最終更新日時を付ける - 比較結果: 変更がなければ
304
,あれば新データを送信
2. 識別子(ETag)での検証:If-None-Match
ヘッダ
- サーバ側:
ETag
ヘッダで識別子を返す - ブラウザ側: 次回リクエスト時の
If-None-Match
にETag
ヘッダを付ける - 一致すれば
304
,不一致なら新しいデータを送る
If-Modified-Since
とIf-None-Match
の両方がある場合は,ETagが優先されます!
柔軟に(広告とブログ間でキャッシュ期間を分けるみたいな)一致条件を制御できるため
プロキシとキャッシュの注意点
ブラウザキャッシュとプロキシキャッシュは別物
キャッシュは基本的にブラウザとプロキシで行われます.この2つは同じに見えますが, 別物として管理する必要があります.
なぜならプロキシキャッシュは複数のユーザに利用されることが多いからです.
- ブラウザキャッシュは個人利用で,利用者本人が管理
- プロキシキャッシュは複数人利用で,プロキシ管理者が管理
例えばクレジットカード番号や認証後のマイページなど,他人に見られたくないページはプロキシにキャッシュさせてはいけません!
したがってキャッシュをどこで扱うかコントロールする必要があります.
それがCache-Control
ヘッダです.
Cache-Control
ヘッダの基本
キャッシュの挙動は,Cache-Control
ヘッダで細かく指定できます.
主にリクエスト時とレスポンス時の2種類に分けられます.
リクエスト時の指定
ブラウザからサーバへリクエストを送る際,すでに保存されているキャッシュを使うかどうか,クライアント側が指定できます!
特にCSSなどが新しく反映されないときにスーパーリロードを使って最新データを強制取得する動作などで使われます.
ディレクティブ | 説明 |
---|---|
no-cache |
検証はするがキャッシュは信用しない(max-age=0 と同等) |
max-age=60 |
60秒以内のキャッシュは使ってもよい |
no-cache
はキャッシュを使わないって意味じゃないの?
よく勘違いしがちなのですが,no-cache
は(正確には)キャッシュを使わないって意味ではありません.
1.リクエスト時はキャッシュを利用せず,Webサーバへ問い合わせます
2-1. レスポンスが200
であり,キャッシュに保存されているものよりも新しいリソースに更新されている場合はそれをレスポンスとして返します
2-2. レスポンスが304
ならキャッシュに保存されているものを利用するのです
一切キャッシュを利用したくなければno-store
を使いましょう
レスポンス時の指定
サーバからブラウザへレスポンスを返す際,かなり複雑で数も多く,状況によってディレクティブの意味が変わります.そのため,キャッシュの再利用に関わる動作だけに注目して解説します.
指定 | 対象 | 説明 |
---|---|---|
no-store |
ブラウザとプロキシ | 一切キャッシュさせない(履歴にも残さない) |
private |
プロキシ | ユーザ単位のキャッシュのみ許可(プロキシには不可) |
public |
プロキシ | 誰が見ても問題ない内容.プロキシ共有可 |
must-revalidate |
ブラウザとプロキシ | キャッシュが切れたら再検証必須 |
max-age=300 |
ブラウザとプロキシ | キャッシュの有効期限(秒) |
s-maxage=600 |
プロキシ | プロキシ用キャッシュの有効期限 |
キャッシュの再利用が決定される時
- 既存キャッシュに対して,キャッシュ更新の検証は必要か
- 検証が不要な場合,キャッシュは新鮮か
- 接続失敗時,キャッシュを使用するか
「検証は必要か?」
- 「検証する」= キャッシュは使うが,サーバに確認をとる(If-Modified-Since や ETag)
-
no-cache
やno-store
があると検証が強制される - キャッシュが更新されて,最新データが表示される
- つまりキャッシュを利用していないことと同等
「キャッシュは新鮮か?」
- キャッシュ期間を超えて古くなっていないかを,サーバ接続前に判断する処理(検証なしでキャッシュを活用するか判断する処理)
「接続失敗時,キャッシュを利用するか?」
- 認証失敗もキャッシュ利用してしまうので,情報漏洩につながる危険性あり
- no-storeなどを利用して強制的にキャッシュさせないことも必要
具体例:キャッシュしつつ安全に情報を扱いたい場合
ユースケース
- 会員マイページのように更新頻度は低い
- ブラウザにはキャッシュしてほしい
- プロキシにはキャッシュしてほしくない
- 認証失敗時に古い情報を出したくない
推奨ヘッダ
Cache-Control: max-age=300, private, must-revalidate
最後に
キャッシュについてまとめました.プロキシやWebブラウザでキャッシュが行われていることはなんとなくわかっていたつもりでしたが,キャッシュ制御の必要性を考慮する必要があるということは目から鱗でした.
HTTPには他にも様々な高速化技術が組み込まれているため今後も勉強していきます.
参考