304 Not Modifiedをhttpレスポンスヘッダで制御する方法(Etag偏)
前回のエントリーでは、304Not ModifiedをIf-Modified-Sinceを設定して制御する方法でしたが、今回はEtagを利用した方法の紹介です。
実は、前回の方法では、不完全な点がありました。それは、キャッシュ期間内に返す画像が変わった(ユーザーがログアウトした)場合、304を引き続き返してしまって表示画像が変わらないという問題です。
ですが、Etagを使うともっとシンプルで、かつ動的な画像でも確実に制御できました。
処理概要
前回同様以下のようなhtmlソースを想定します。
<img src="/img/1" />
で、同じURLなのですが、ログイン前はサンプル画像を返し、ログイン後には通常画像を返す、という処理になります。
レスポンスヘッダの設定
今回、Etagのみに注目するため、他のヘッダは極力設定してません。
$defalut_headers["Etag"] = $this->headerEtag($file_path);
$file_pathには、実ファイルのパスが入ります。
headerEtagメソッドの中身は、こちら(PHP で Apache2 ライクな ETag を生成する)を参考にapache風etagを実装してみました。
function headerEtag($file_path = null){
if($file_path === null){
return false;
}
$lm_time = getlastmod();
$stats = stat($file_path);
return sprintf( '"%x-%x-%x"', $stats['ino'], stats['size'], $stats['mtime'] * 1000000 );
}
Etagに関してはあくまでコンテンツを比較するための識別子で、とくにフォーマットは無いようですが(参考:w3:14 Header Field Definitions)、なんとなくapache風にしておきましたw
ただし、Etagにinodeを利用している場合、webサーバが複数になるとキャッシュして欲しい時に別のwebサーバのinodeが利用され($stats[‘ino’]が違う値になる)、キャッシュしない場合があるので気を付けましょう。
304 Not Modified 判定処理
Etagを設定すると、$_SERVER[‘HTTP_IF_NONE_MATCH’]が飛んできますので、以下のようなphpソースで判定します。
if(isset( $_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $this->headerEtag($file_path)){
header( 'HTTP/1.1 304 Not Modified' );
header( 'Content-Type:' . $option_headers["Content-Type"] );
exit();
}
レスポンスヘッダの中身
今回は、以下の流れで見てきます。
非ログインで(=サンプル画像を)、リクエスト
サンプル画像を再リクエスト
ログイン後(取得画像が通常画像に変更後)、リクエスト
引き続きログイン状態で、通常画像を再リクエスト
リクエスト1回目
まずログインしていない、サンプル画像の1回目の取得から見ていきます。
GET /img/1 HTTP/1.1
Host: domain
Pragma: no-cache
Cache-Control: no-cache
CTRL+F5の強制リロードでリクエストしています。
レスポンス1回目
HTTP/1.1 200 OK
Date: Thu, 18 Feb 2010 04:26:26 GMT
Etag: “3c2bd9-607-545031c0″
Content-Type: image/gif
無事Etagが付与されて返ってきました。
リクエスト2回目
Etagの効果を検証するため、同じ画像に再度リクエストします。
GET /img/1 HTTP/1.1
Host: domain
If-None-Match: “3c2bd9-607-545031c0″
If-None-Matchヘッダが追加されていますね。値は1回目のレスポンスにあったEtagの値になっています。
レスポンス2回目
HTTP/1.1 304 Not Modified
Date: Thu, 18 Feb 2010 04:26:59 GMT
Content-Type: image/gif
無事、先ほどの304判定処理を通って、304が返ってきました。
リクエスト3回目
ログインしたのちリクエストします。
GET /img/1 HTTP/1.1
If-None-Match: “3c2bd9-607-545031c0″
レスポンス3回目
HTTP/1.1 200 OK
Date: Thu, 18 Feb 2010 04:27:12 GMT
Etag: “3c2bce-973-9ed39c0″
Content-Type: image/gif
**304では無く200になって、Etagに異なる値が返ってきてます。**これは、
サーバ側でリクエスト受け取る
ログインしているため返す画像が変わった
$filepathが変わりEtagが変わった
304判定を通らなかった
デフォルトのレスポンスが返った
という流れになっています。
リクエスト4回目
GET /img/1 HTTP/1.1
Host: domain
If-None-Match: “3c2bce-973-9ed39c0″
新しいEtagの値が利用されてますね。
レスポンス4回目
HTTP/1.1 304 Not Modified
Date: Thu, 18 Feb 2010 04:27:32 GMT
Content-Type: image/gif
Etagとマッチし、304が返ってきました。
まとめ
Etagを使うと前回のように期間なんて指定しなくても、動的に変わる画像を正しく304返す動きが実現できました。じゃあ、最初からEtag使っとけよって話ですがw、auでは使えないようなのです。。。(参考:KDDI au: XHTML Basicについて > キャッシュコントロール)
ということでIf-Modified-SinceとEtagの併用が、携帯にはよさげです。
ただ、、、Etag使えない場合でも、もっと精度よくキャッシュ制御する方法ないですかね。。。あったら教えてくださいw