メインコンテンツへ移動

HTTP キャッシュ

無料2017-09-10#HTTP#HTTP cache header#HTTP cache-control#browser default cache#浏览器默认缓存#HTTP缓存策略

明らかに強力なキャッシュにヒットしたのに、なぜまだリクエストを送信するのか?

一.分類

キャッシュの強さによって分類:

  • 強力なキャッシュ:有効期間内は、リソースを直接ローカルキャッシュ(disk cache または memory cache)から取得;有効期間外または強制更新時、server から再度取得

  • 交渉キャッシュ:有効期間内は同上;有効期間外または強制更新時、ローカルバージョン番号を付けて server にリソースの更新があるか問い合わせ、304(キャッシュ有効期限などを更新し、ローカルバージョン继续使用)または 200(新バージョンをキャッシュし、ローカルバージョンを破棄)の応答を取得

交渉キャッシュはさらに細分化可能:

  • 時間ベース:リソース修正時間(Last-Modified)をバージョン番号とする

  • コンテンツベース:リソース内容の hash(ETag)をバージョン番号とする

交渉はキャッシュが無効(期限切れまたは廃棄)された後に発生する

二.関連 Header フィールド

HTTP Header フィールドは 4 種類に分類:

  • general-header(一般ヘッダー):リクエストとレスポンスメッセージの両方に適用

  • request-header(リクエストヘッダー):client が server に追加情報を送信可能、リクエスト修飾子、パラメータと同様の役割

  • response-header(レスポンスヘッダー):server が client に対してこのレスポンスに関する追加情報を送信可能、server 関連の情報および将来このリソースにアクセスする際に必要な情報を含む

  • entity-header(エンティティヘッダー):メッセージエンティティ関連の meta 情報を提供、メッセージエンティティがない場合は、リクエストに対応するリソースの情報

P.S.HTTP Header の詳細情報は 4.2 Message Headers を参照

Pragma

HTTP 1.0 一般ヘッダーフィールド、キャッシュポリシーを指定

Pragma           = "Pragma" ":" 1#pragma-directive
pragma-directive = "no-cache" | extension-pragma
extension-pragma = token [ "=" ( token | quoted-string ) ]

Pragma は意味が曖昧なフィールドで、RFC は Pragma: no-cache がリクエストに現れる場合、キャッシュが有効でもオリジンから新しいものを取得すべきと指定のみ。レスポンスに現れる場合、明確な意味はない

P.S.Pragma の詳細情報は 14.32 Pragma を参照

Expires

HTTP 1.0 エンティティヘッダーフィールド、リソースの有効期限を表し、期限切れポリシーを指定

Expires = "Expires" ":" HTTP-date

正確な时间点で、それ以前はキャッシュ有効。この时间点は server が提供し、client と server の時間が同期していない場合、キャッシュ期限切れポリシーは信頼できなくなる

Expires が示す时间点が client と server で同じ時刻に対応することを保証できないため、HTTP 1.1 では Cache-Control: max-age=<seconds> を通じて有効期限を定義可能。时间段を提供し、client がリソースを取得した後、seconds 秒でキャッシュ期限切れ。これにより client 時間のみに依存し、一貫性を要求しない

Cache-Control

一般ヘッダーフィールド、キャッシュポリシーと期限切れポリシーを指定

Cache-Control   = "Cache-Control" ":" 1#cache-directive
cache-directive = cache-request-directive
    | cache-response-directive
cache-extension = token [ "=" ( token | quoted-string ) ]

レスポンスヘッダーには 9 つの値が現れ得る:

cache-response-directive =
    ; リソースはクライアントとプロキシサーバーによってキャッシュされる
    "public"
    ; リソースはクライアントのみによってキャッシュされ、プロキシサーバーによるキャッシュは許可されない
    | "private" [ "=" <"> 1#field-name <"> ]
    ; オリジンから再確認しない場合、リソースの再利用は許可されない
    | "no-cache" [ "=" <"> 1#field-name <"> ]
    ; リソースのキャッシュ書き込みは許可されない
    | "no-store"
    ; プロキシサーバーによる Content-Encoding、Content-Range、Content-Type フィールドの変更を禁止
    | "no-transform"
    ; 期限切れリソースの使用は許可されない、一旦期限切れ、オリジン検証必須(クライアントが期限切れリソースを受け入れる場合でも)
    | "must-revalidate"
    ; public に依存、must-revalidate に類似、プロキシサーバーのみに適用
    | "proxy-revalidate"
    ; リソースをキャッシュ、但し指定時間(秒単位)後にキャッシュ期限切れ
    | "max-age" "=" delta-seconds
    ; public に依存、プロキシサーバー上でのみ有効、max-age をオーバーライド
    | "s-maxage" "=" delta-seconds
    ; カスタム拡張値
    | cache-extension

リクエストヘッダーには 7 つの値が現れ得る:

cache-request-directive =
    ; 強制オリジン、キャッシュからのコンテンツは不要
    "no-cache"
    ; クライアントリクエスト関連情報をキャッシュに書き込むことを許可しない
    | "no-store"
    ; クライアントは age(プロキシサーバーキャッシュ時間)が delta 秒を超えないリソースを受け入れる
    | "max-age" "=" delta-seconds
    ; クライアントは delta 秒以内の期限切れ旧コンテンツを受け入れる
    | "max-stale" [ "=" delta-seconds ]
    ; クライアントは delta 秒内に応答コンテンツが有効であることを希望
    | "min-fresh" "=" delta-seconds
    ; クライアントは変換されたコンテンツ(例:Content-Type)を受け入れない
    | "no-transform"
    ; クライアントはキャッシュ済みリソースのみを希望、リソースを再リクエストしない
    | "only-if-cached"
    ; カスタム拡張値
    | cache-extension

no-store, no-cache, must-revalidate の記述間の微妙な差異に注意、同一フィールドがリクエストヘッダーとレスポンスヘッダーに現れる場合の意味も異なる

Last-Modified

エンティティヘッダーフィールド、リソースの最終修正時間を表し、交渉ポリシーを指定

Last-Modified  = "Last-Modified" ":" HTTP-date

クライアントは取得後に保存し、次回 server にリソースをリクエストする際、この时间点をバージョン番号として付け、ローカルキャッシュリソースが引き続き利用可能か検証

リソースが修正されても内容が変わらない場合、内容が同じレスポンスを送信するのは冗長なので、コンテンツベースの交渉キャッシュも提供され、この状況を回避

P.S.Cache-Control: max-age より優先度が低く、同時に現れる場合は max-age が優先

If-Modified-Since

リクエストヘッダーフィールド、時間ベースの交渉ポリシー実装に必要、リソース最終修正時間(Last-Modified、リソース最終修正時間)が一致するか比較

If-Modified-Since = "If-Modified-Since" ":" HTTP-date

Last-Modified バージョン番号をフィールド値として server に送信、リソースが更新されていなければ 304 を返しレスポンス本体なし、更新されていれば 200 を返し新バージョン内容をレスポンス本体として送信

If-Unmodified-Since

同上、動作は逆(リソース最終修正時間が一致しないか比較)、一致せず method が POST/PUT などの更新操作の場合、412 を返す(Precondition Failed、条件不満足)更新実行失敗を示す

ETag

レスポンスヘッダーフィールド、リソースの内容 hash を表し、交渉ポリシーを指定

ETag = "ETag" ":" entity-tag

クライアントはこの値を記録し、次回このリソースをリクエストする際バージョン番号として server に送信

P.S.ETag の優先度は Last-Modified より高い

If-Match

リクエストヘッダーフィールド、コンテンツベースの交渉ポリシー実装に必要、このフィールドの値(ETag、リソース内容 hash)が一致するか比較

If-Match = "If-Match" ":" ( "*" | 1#entity-tag )

一致せず、method が POST/PUT などの更新操作の場合、412 を返し更新失敗を示す

If-None-Match

同上、動作は逆(このフィールドの値が一致しないか比較)、一致すれば 304 を返しクライアントにキャッシュバージョ���继续使用可能と通知、否则新リソースを返す

Age

レスポンスヘッダーフィールド、リソースがプロキシサーバー上でキャッシュされた時間を表す

Age = "Age" ":" age-value
age-value = delta-seconds

計算方式:

/*
 * age_value
 *      is the value of Age: header received by the cache with
 *              this response.
 * date_value
 *      is the value of the origin server's Date: header
 * request_time
 *      is the (local) time when the cache made the request
 *              that resulted in this cached response
 * response_time
 *      is the (local) time when the cache received the
 *              response
 * now
 *      is the current (local) time
 */

apparent_age = max(0, response_time - date_value);
corrected_received_age = max(apparent_age, age_value);
response_delay = response_time - request_time;
corrected_initial_age = corrected_received_age + response_delay;
resident_time = now - response_time;
current_age   = corrected_initial_age + resident_time;

Age:0 は直前にオリジン server から取得したことを示し、正の値は前回オリジンから取得してから現在までの秒数を示す

三.強力なキャッシュと交渉キャッシュ

それぞれキャッシュの異なる段階で発生、キャッシュ有効時は強力なキャッシュ、リクエストを送信せず、キャッシュ無効後初めて交渉キャッシュ、リクエストを送信しリソース更新の有無を問い合わせ

強力なキャッシュ

レスポンス内容が強力なキャッシュにヒット後、キャッシュ有効期間内、ブラウザは server にリクエストを発行せず、直接ローカルキャッシュ(disk cache または memory cache)から読み取り

ローカルに該リソースのキャッシュバージョンがあり、Cache-Control: max-age または Expires が期限切れでなければ、強力なキャッシュにヒット

交渉キャッシュ

キャッシュ期限切れ後、該リソースに再度アクセスする際、ブラウザはローカルキャッシュバージョン番号を付けて server に問い合わせ、server はクライアントが送った ETag または Last-Modified 値を検査し、クライアントにキャッシュを更新するか否かを通知

レスポンスヘッダーの ETagLast-Modified は交渉キャッシュのスイッチ、交渉キャッシュの利点は内容が変わらない場合、直接 304 を返し、レスポンス本体を送信不要

四.啓発的キャッシュ

比較的特殊的な状況はレスポンスヘッダーがキャッシュ関連情報を全く提供しない場合、この時ブラウザは啓発的アルゴリズムを使用してリソースキャッシュ期限を確定:

max-age = Date - Last-Modified / 10

デフォルトのキャッシュポリシー、啓発的キャッシュと呼び、啓発的とは経験に基づいて構築されたもので、厳密な根拠はない

五.リフレッシュ動作

ブラウザには 3 種類の異なるリフレッシュ動作があり、HTTP キャッシュを検証する際に混乱しやすい:

  • 新ページを開く:新タブまたはウィンドウを開き、ページにアクセス

  • 通常リフレッシュ:リフレッシュボタンクリック、アドレスバー Enter、CMD + R

  • 強制リフレッシュ:CMD + Shift + R、Chrome でリフレッシュボタン長押し、ハード再読み込み選択

  • キャッシュ無効化して再リフレッシュ:Disable cache 設定をチェック、その後新ページ/リフレッシュ

新ページを開く

リクエストヘッダーにキャッシュ関連フィールドを付与せず、ローカルキャッシュバージョンが有効な場合、キャッシュから読み取り、リクエストを送信せず、偽リクエストヘッダーを表示:

Request Headers
    Provisional headers are shown
    Upgrade-Insecure-Requests:1
    User-Agent:...

レスポンスヘッダーはキャッシュの那份继续使用

通常リフレッシュ

キャッシュから取得せず、必ずサービスにリクエストを発行、リクエストヘッダーに If-Modified-SinceIf-None-Match などのキャッシュヘッダー(ある場合)を付与、さらに勝手に追加:

Cache-Control:max-age=0

プロキシサーバーにキャッシュが期限切れか確認を要求

P.S.通常リフレッシュ動作が発生する際、ブラウザは必ずリクエストを発行、リソースキャッシュが依然有効で、強力なキャッシュ状態にあるべき場合でも。ユーザーは内容のリフレッシュを要求し、新しいものを見たい希望があり、関連リソース(例えば該ページに含まれる CSS、JS などのリソース)は強制リクエストを発行されない

強制リフレッシュ

同様に強制リクエストを発行、キャッシュ関連情報を付与、さらに勝手に追加:

Cache-Control:max-age=0
Pragma:no-cache

オリジンから新しいものを取得要求、キャッシュが期限切れでなくても

キャッシュ無効化して再リフレッシュ

キャッシュ無効化後、後続のすべてのリクエストに追加:

Cache-Control:max-age=0
Pragma:no-cache

全て強制リフレッシュを実行するのと同等、関連リソースを含む

P.S.Cache-Control:max-age=0Pragma:no-cache の具体的動作は server 実装に依存、実際にはプロキシサーバーは必ずしもオリジンに戻ったり期限切れを確認したりしない

参考資料

コメント

コメントはまだありません

コメントを書く