備忘録: Apacheから304が返ってこないときの対処方法

はじめに

Apache 2.4のサーバーから304 (Not Modified) のレスポンスが返るべき状況で、常に200 (OK) が返るという問題に遭遇した。その解決方法について調べたので備忘録を残す。

問題

パフォーマンス対策の一環として、Cache-Controlno-cacheを指定した。対象はHTMLとJavaScriptのファイル。この場合ファイルに変更が無い場合は、サーバーは304を返すはずである。しかし対象のサーバーでは、コンテンツに変更が無いにも関わらず、常に200を返してきた。

原因

調べてみるとこの現象はApacheのバグとして報告されている。最初の報告は2008年なので「本当に今でも未解決なのか?」と驚いたが … 経緯が色々とあるらしい。

参照: Bug 45023 – DEFLATE preventing 304 NOT MODIFIED response

ApacheのディレクティブでDEFLATEが指定されていると、サーバーからWebブラウザにコンテンツを圧縮して送り出す。この時Etag-gzipが追加される。このEtagは次回のアクセス時に、WebブラウザからIf-None-Matchで送り返される。サーバーはこのEtagの値を現在のEtagの値と比較して、変更の有無を判断するが、Etag-gzipが含まれていると正しく比較できず、コンテンツに変更があったと誤って判断する(と一応理解した)。

筆者の環境はレンタルサーバーのため、DEFLATEの設定については確認できていない。しかしレスポンスにContent-Encoding: gzipが含まれており、Etagの末尾に-gzipも追加されていることから、上記の説明に該当するものと判断した。

mod_deflate - Apache HTTP サーバ バージョン 2.4

解決策

対象のファイルを置いてあるディレクトリに、.httaccessファイルを作成し、Bug 45023のComment #26に基づいて次を記述した。

<IfModule mod_headers.c>
RequestHeader edit "If-None-Match" '^"((.*)-(gzip|br))"$' '"$1", "$2"'
</IfModule>

Webブラウザからのリクエストヘッダーに含まれているETagから-gzipを削除している。

-brも指定されているが、これは別の圧縮方式で追加されるものである。-gzipと同様の問題を引き起こすものと思われるが、筆者の環境では未確認。

対象をより限定する場合は、Directory, Location, Filterなどを併用する必要がある。

なおApache 2.5では、DeflateAlterETag Directiveを使うことで、この問題を回避できるとある。そこで2.5のドキュメントを見てみると、このディレクティブは2.4.42以降で使えるとある。しかし2.4のドキュメントには記載がない。実際に使えるかどうか筆者は未確認。

mod_deflate - Apache HTTP Server Version 2.5

キャッシュコントール

Cache-Controlは、サーバーからのレスポンスヘッダーのひとつで、Webブラウザのキャッシュを制御することができる。キャッシュコントロールは、目的に応じて複数の方法から指定可能となっている。

Cache-Controlno-cacheを指定すると、Webブラウザーは毎回サーバーからコンテンツの取得を試みる。サーバーはコンテンツに変更があれば、200と新しいコンテンツを返す。変更が無い場合は、304のみを返しコンテンツの再送は行わない。Webブラウザは、304を受け取ると、自身のキャッシュ内のコンテンツを使ってレンダリングを行う。

キャッシュを全くしないと誤認されている場合があるので要注意である。

変更履歴

日付内容
2023/05/14初版リリース