HTTP

Cache-Controlヘッダは仕様通り実装されていない?

More than 1 year has passed since last update.

最初に

次のエントリーで追試しました。本エントリーの内容は古いです。一応Qiitaは履歴もとってくれるのでこの記事を上書きしちゃってもいいんですが、そうなるとコメントのコンテキストがわからなくなってしまうので、別記事にしました。本エントリーも記録のために残します。

本編

HTTPのキャッシュの仕組みをいろいろ調べているのですが、よくわからなかったので実験してみました。

上記のサイトの説明によれば、no-cacheとmust-revalidateは非常に近い説明になっています。no-cacheはsubsequent requestと書いてあるので、.htmlから呼ばれる.css、.jsあたりのことまで(subsequent request)書いていると思われます。

どちらも、オリジンサーバに確認しないとキャッシュされているファイルを使っちゃダメよ、ということが書かれています。つまり、どちらも、一度キャッシュされたコンテンツのリクエスト依頼がくると、

  • キャッシュされている?
    • キャッシュ時にサーバから送られてきたEtagヘッダーの内容を、If-None-Matchヘッダーにいれてリクエスト
    • 304だったら、変化がないのでキャッシュから読み込んで、指定されたファイルを読み込み完了とする
    • 304以外なら、そのままボディにレスポンスが返ってくるはず
  • キャッシュされていない?
    • そのままリクエストをサーバに送る

こんな感じのフローになっているんじゃないかと思います。ちなみに、絶対に名前選択ミスだと思うんですが、no-cacheはキャッシュは行います。キャッシュをしてはならない、と指定するには"no-store"をCache-Controlヘッダに設定します。

no-cache

If the no-cache directive does not specify a field-name, then a cache MUST NOT use the response to satisfy a subsequent request without successful revalidation with the origin server. This allows an origin server to prevent caching even by caches that have been configured to return stale responses to client requests. If the no-cache directive does specify one or more field-names, then a cache MAY use the response to satisfy a subsequent request, subject to any other restrictions on caching. However, the specified field-name(s) MUST NOT be sent in the response to a subsequent request without successful revalidation with the origin server. This allows an origin server to prevent the re-use of certain header fields in a response, while still allowing caching of the rest of the response.

must-revalidate

Because a cache MAY be configured to ignore a server's specified expiration time, and because a client request MAY include a max- stale directive (which has a similar effect), the protocol also includes a mechanism for the origin server to require revalidation of a cache entry on any subsequent use. When the must-revalidate directive is present in a response received by a cache, that cache MUST NOT use the entry after it becomes stale to respond to a subsequent request without first revalidating it with the origin server. (I.e., the cache MUST do an end-to-end revalidation every time, if, based solely on the origin server's Expires or max-age value, the cached response is stale.)

実験内容(1)

コードはこちらです。

index.htmlと、それからリンクされているtest.jsを返すサーバです。起動時のオプションによって、下記の項目の組み合わせが変化するようになっています。

  1. index.htmlのEtagの有無
  2. test.jsのEtagの有無
  3. index.htmlのCacheControlの設定値(下記の項目のどれか)
  4. index.htmlのCacheControlの設定値(下記の項目のどれか)

組み合わせは、2 x 2 x 4 x 4で64通りあります。

"no-cache, max-age=5",
"no-cache, max-age=0",
"must-revalidate, max-age=5",
"must-revalidate, max-age=0",3. 

それぞれのケースで、キャッシュクリア後、初回ロード、2回めロード(数秒以内)、時間を開けて3回目ロードという三回リロードしています。

で、こちらが結果になります。使っているブラウザは、今日現在で最新のSafari、Chrome、Firefox Developer Editionです。面倒になって全部はやってませんが・・・

実験1の結果としては

  • Cache-Controlがno-cache, must-revalidateのどれが設定されているかで結果は変わらない
  • Etagがサーバから送付されたかどうかだけが結果に影響を与えている
  • Safariに関しては、index.htmlのキャッシュがまったく効かない

Cache-Controlがまったく効いてないぞ・・・・

実験内容(2)

テスト用サーバはほぼ同じで、次は、no-store、max-age=86400をCache-Controlに設定してみつつ実行してみました。結論としては、

  • no-storeが設定されていればキャッシュされない(Etagがあっても無視される)
  • max-ageで指定されると、更新確認のリクエストも送信されないとされているけど、まったく無視される。

というものでした。

結論

  • Cache-Controlは、no-storeが含まれているかどうかだけが問題というブラウザ実装になっているっぽい。他の要素は仕様には書かれているけど、まったく効果がないノイズのように見えているのかもしれません。
  • no-store以外が設定されていて保存したい場合には、Etagヘッダも一緒に指定すること(デフォルトではファイルのハッシュ値が表示もされないし見えない)。

なんかおかしい気もするので、見落としがあったら指摘してもらえると助かります。