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

· application, ネットワーク, 携帯

前回のエントリーでは、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

画像未復旧: hatena.gif

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