304 Not Modifiedをhttpレスポンスヘッダで制御する方法(Etag偏)304 Not Modifiedをhttpレスポンスヘッダで制御する方法(Etag偏)

2010/02/18

前回のエントリーでは、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. 非ログインで(=サンプル画像を)、リクエスト
  2. サンプル画像を再リクエスト
  3. ログイン後(取得画像が通常画像に変更後)、リクエスト
  4. 引き続きログイン状態で、通常画像を再リクエスト

リクエスト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に異なる値が返ってきてます。これは、

  1. サーバ側でリクエスト受け取る
  2. ログインしているため返す画像が変わった
  3. $filepathが変わりEtagが変わった
  4. 304判定を通らなかった
  5. デフォルトのレスポンスが返った

という流れになっています。

リクエスト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

タグ: , , ,

関連があるかもしれないエントリー

コメントをどうぞ