PHP で Apache 風 ETag の生成
Ogawa::Memoranda の「条件付きGET」のススメというエントリをヒントに、新しくひらめいた。Apache は PHP モジュールを通したコンテンツの ETag を生成してくれない。Last-Modified HTTP ヘッダは以前から送信していたが、ETag に関しては詳細を知らなかったため放置していたのだ。ETag の概要はある種のハッシュ値であると把握していたが、詳しくどういう規格なのか知らなかった。Ogawa::Memoranda で紹介されているコードを読んで ETag が実は単なる「RFC 822 形式な最終更新日時の MD5 ハッシュ」だったことを知り驚いた。しかし、そのソースコードを簡単に摘まんで試してみると、Apache が生成してくれる ETag とは文字数が違っていた。それ以前に、改めて見ると Apache が生成する ETag はハイフンで区切られた文字列であり、MD5 ハッシュではなかった。そして、どうやら ETag には特に決められた規則はないらしい。つまり、どちらも正解ということだ。
調べてみると Apache 方式の ETag は stat システムコールから得た i-node 番号とファイルサイズ、最終更新日時から生成されていることがわかった。私はデファクトスタンダードな Apache 式の ETag が好みなので次のような PHP を書いた。
<?php
$lm_time = getlastmod();
$stats = stat( $_SERVER['SCRIPT_FILENAME'] );
$etag = sprintf( '"%x-%x-%x"', $stats['ino'], $stats['size'], $stats['mtime'] );
if ( ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) &&
strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) >= $lm_time ) ||
( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) &&
stripcslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) == $etag ) ) {
header( 'HTTP/1.1 304 Not Modified' );
exit();
}
header( 'Last-Modified: ' . gmdate( "D, d M Y H:i:s T", $lm_time ) );
header( 'Expires: ' . gmdate( "D, d M Y H:i:s T", $lm_time + 1800 ) );
header( 'ETag: ' . $etag );
?>
さて、調査不足なのか、このコードを用いても同じソースから生成した ETag が Apache と一致しない。一致しないのはハイフン区切りで 3 つめの 最終更新日時から生成した部分(ex. Etag: "3f49c5-6e62-41e2a1c7")。Apache は stat システムコールの mtime を使っていないのだろうか。それとも PHP 側の問題だろうか。原因不明だけれどとりあえず満足のいく ETag が生成できるようになったのでこれにて保留。そんなわけで Apache 風です。
- タグ
- ETag
- PHP
- 公開日時
- 2005-01-10T21:17:48+09:00 @554
- 更新日時
- 2005-01-29T14:58:32+09:00 @290
- Permalink URI & TrackBack URL
- http://blog.drry.jp/2005/01/10/2117
コメント ( 2 )
ナイスです。
doConditionalGetは単一のタイムスタンプだけからETagを生成しますが、それは汎用性のためなんです。ローカルファイルならばApache風のETagを生成してもよいですが、DBの特定のテーブルの更新日時をLast Modifiedとしたいという需要も一般的にありますからね。
なるほど、なるほど。確かにその通りですよね。ETag が自由度の高い書式なのも納得できます。