HTTPのVary、同じURLの応答を取り違えないための照合条件

淡い緑と桃色の、滑らかな器と縦溝のある器が、それぞれのくぼみへ収まる四つの組合せを表した抽象的な構図。

同じURLへアクセスしても、日本語の本文と英語の本文、圧縮した本文と圧縮していない本文が返ることがあります。このとき、キャッシュがURLだけを手がかりに以前の応答を使うと、別の条件で選ばれた内容を渡してしまいかねません。HTTPのVaryは、応答を選ぶ際にどの要求ヘッダーが関係したかを伝え、保存した応答を次の要求へ使えるか判断するための情報です。[1][2]

簡単にまとめると?

  • Varyは応答側のヘッダーです。そこに列挙するのは、応答の選択に関係する要求側のヘッダー名です。
  • Varyで指定された要求条件が合わなければ、保存済み応答をそのまま再利用できません。新鮮さや保存許可とは別の確認です。
  • 必要な条件を省くと別の表現を返す原因になり、不要な条件を増やすと再利用の機会を減らします。
  • Varyはアクセス制御や秘密保持の設定ではありません。認証、共有保存の可否、表現の選択条件を分けて点検します。

応答に書くのは、要求側の条件の名前

たとえば、サーバーが要求のAccept-Languageを見て言語を選び、Accept-Encodingを見て圧縮形式を選んだとします。応答に Vary: Accept-Language, Accept-Encoding を付けると、キャッシュはその二つの要求ヘッダーも照合に使います。Varyへ「日本語」や「gzip」という選択結果を書くのではなく、選択に関係した欄の名前を書く点が大切です。[1、12.5.5節]

ここでの表現は、同じ対象を利用者へ渡す際の具体的な形です。案内ページという対象は同じでも、言語や符号化が違えば、送るバイト列や応答のメタデータは違います。キャッシュキーとは、保存した応答と新しい要求を対応づけるための手がかりです。Varyは、その照合条件を要求メソッドと対象URI以外にも広げます。

保存済み応答にVaryがある場合、列挙された要求ヘッダーが、保存のきっかけになった要求と一致しなければ、再検証なしでその応答を使ってはいけません。一致する保存応答がなければ、通常は要求を元のサーバーへ転送します。保存済みの表現を示す条件を付けて問い合わせることもあります。[2、4.1節]

八回の要求で、条件の不足と増やしすぎを比べる

小さな独自モデルで違いを見ます。同じGET /guideに、日本語・英語と、gzip・identityを組み合わせた四種類の要求を順に送り、その並びをもう一度繰り返します。identityはこの例では圧縮しない表現を選ぶ指定です。要求の言語欄は単純なjaかenだけ、圧縮欄も単独の値とし、優先度や複数候補は扱いません。

キャッシュは最初は空で、応答はすべて保存可能かつ新鮮、途中で内容は変わらず、追い出しもないと仮定します。元のサーバーが返す表現を「言語と圧縮形式の組」として表し、要求が期待する組と、実際にキャッシュから受け取った組を比較します。これはキー設計を理解する計算であり、実ブラウザーや配信サービスの性能測定ではありません。

Varyを省いた場合は、モデルが最初の日本語・gzipの応答を一つだけ保存します。次の七回はすべてキャッシュヒットですが、同じ条件が再び来る一回を除き、六回は要求と違う組を受け取ります。「ヒット率が高い」だけでは、正しく配れたとは判断できない例です。

言語と圧縮形式を照合する場合は、最初の四回で四種類の応答を取得します。二巡目はそれぞれの保存応答を使えるため、元のサーバーへの要求は四回、キャッシュヒットも四回です。八回とも期待する組になります。

さらに毎回異なる識別子までVaryに加える場合は、八回とも別のキーになります。この例のX-Debug-Idは、要求ごとに違う文字列を付ける架空の診断用ヘッダーで、本文の選択には使いません。それでもVaryで指定すると、このモデルでは前の応答と一致しなくなり、元のサーバーへ八回要求して、キャッシュヒットはゼロです。表現は正しくても、不要な条件が再利用を妨げます。

八要求の独自モデル。Varyなしは取得1回・ヒット7回・誤った表現6回。言語と圧縮形式を指定すると取得4回・ヒット4回・誤り0回。毎回違う診断IDも加えると取得8回・ヒット0回・誤り0回。
同じURLへ四種類の要求を二巡させた説明用モデル。保存応答はすべて新鮮とし、検証通信・容量制限・実際の圧縮量は含めていない。ヒット数だけでなく、正しい表現が返ったかを併せて見る。

何を同じとみなすかは、ヘッダーの仕様に従う

現実の照合は、すべてのヘッダーを単純な文字列として比べればよい、というものではありません。RFCは、各欄の構文で許される空白の調整、同名の複数行の結合、意味が同じと分かる正規化を認めています。値の順番を変えてよいか、大文字と小文字を同一視できるかは、そのヘッダーの定義に依存します。何でも並べ替えたり小文字にしたりする規則ではありません。[2、4.1節]

また、要求にそのヘッダーがなかった場合は、同じく存在しない要求とだけ一致します。「指定なし」を勝手に特定の値へ置き換えると、元のサーバーが使った既定値の選択と食い違う可能性があります。前のモデルが単独の値だけを使ったのは、この問題とキーの数の問題を切り分けるためです。

Vary: *にも注意が必要です。これは「あらゆる要求に一致する」という印ではなく、要求メッセージの外も含む選択条件が関係し得ることを示します。キャッシュの照合では常に不一致になり、保存応答を適切に使えるかは元のサーバーへ要求を転送しないと判断できません。保存そのものの禁止を意味するno-storeとは役割が異なります。[1][2]

新しい応答でも、別の要求向けなら使えない

max-ageのような新鮮さの条件は、「いつまで確認なしで使えるか」を扱います。Varyは「どの要求に対応する応答なのか」を扱います。日本語向けの保存応答がまだ新鮮でも、英語を指定した要求へそのまま使えるとは限りません。逆に要求条件が一致しても、古くなっていれば再検証など別の条件を満たす必要があります。

設定を点検するときは、まず元のサーバーが実際に何を見て表現を選ぶかを洗い出します。次に、その違いをVaryや別のURL設計へどう反映するかを確認します。最後に、保存許可、新鮮さ、再検証の指示を照合します。期待する本文が同じ二つの要求を別キーへ分けていないか、違う本文が必要な二つの要求を同じキーへまとめていないか、対になるケースを用意すると確認しやすくなります。

その際、認証や利用者別データをこの比較モデルに安易に含めてはいけません。Varyは、アクセス権の確認や共有キャッシュへの保存禁止の代わりにはなりません。本稿の例は利用者共通の案内ページであり、秘密情報の配信設計を検証したものではありません。実装ごとのキー設定やヘッダーの書換えも含め、最終的に返された本文・言語・符号化と、元のサーバーへ問い合わせた回数を一緒に観測することが必要です。

出典

  1. RFC 9110「HTTP Semantics」(2022年6月、STD 97)、12.5.5節「Vary」。公式本文
  2. RFC 9111「HTTP Caching」(2022年6月、STD 98)、4.1節「Calculating Cache Keys with the Vary Header Field」、4.2節「Freshness」。公式本文。八要求の件数と比較図は、明示した仮定によるQuantum Seaの独自モデルです。