phpでEtag処理

去年、モジュール版PHPで「If-Modified-Since」に対応するをみて、Etag用のクラスをつくったのを思い出したのでひっぱり出してきました。

Etagが重要になってくるのは中規模以上のサイトなので、気楽に使うモジュール版のphpでやる必要はねーじゃんか、なんてスタンスもありなんですけど、やれることはやっちゃっていいのです。

ネットを見回してると、わざわざapacheのデフォルト設定を真似して、iNode-Mtime-Sizeって形で吐いてる人もいるんですが、これは

  • ロードバランシングしたときiNodeの値は各サーバーで揃わないので、Etagの意味がなくなる
  • iNodeがバレるのが(不要なリスクであるため)セキュリティ上好ましくないといわれたりする

なんてのがあるので注意が必要です。この記事のEtagクラスでは、任意の文字列に対するmd5ハッシュでEtagをつくることにしています。

ソースコード

じゃ早速Etag処理用クラスです。
テキストファイルで置いてますので、.phpなり.incなりにリネームして使ってください。
http://www.ryo.com/files/Etag.class.txt
※ クラス関数parse_http_date()は前述のArielworksさんのものを拝借しました、また、各種DBなどの日付時刻型もparseしたかったので、最後にstrtotime()でparseするように修正してます。
※ Arielworksさん同様に特にcopyrightは主張しませんので、勝手気ままに使ってください。

使い方

こんな感じです

require_once('Etag.class.php');
$etag = new Etag($_SERVER['REQUEST_URI'] . $upd_dt . @filemtime('this.php')
                 , array($last_upd_dt, @filemtime('this.php'));
if ($etag->etagCheck()) {
    exit;
}

※ $upd_dt はドキュメントが参照するDBのレコード郡の最終更新日時、this.phpがこのphpファイルと仮定してます。

テンプレートシステムを導入していて、htmlの出力前に表示以外の処理を終わらせるつくりになっているスクリプトなら、ちょっと足すだけでEtagに対応できるわけです。
※ コンストラクタでetagCheck()までやってしまえば2行で済むんですが、クラスの中からexitするのが乱暴なのと、スクリプト側の自由度が減るのはよろしくないので、外でやるようにしてるワケです

ちょっと詳しく説明

コンストラクタとクラス関数について説明しときますね。

Etagコンストラクタ

EtagのコンストラクタにはEtagおよびLast-Modifiedヘッダのための2つの引数が必要です。
Etagコンストラクタは、渡された引数から現在のドキュメントのEtagおよびLast-Modifiedヘッダを決定し、クライアントから送信されたIf-None-MatchおよびIf-Modified-Sinceを解析し、クラス内に保持します。

string $key

1つめは、Etagを生成する元となる文字列です。etagCheck()はこの文字列をもとにmd5を算出し、’Etag’ヘッダを出力します。
ドキュメントが変更されたらEtagも変更されるように工夫して渡してあげましょう。
例ではリクエストされたURIと最終更新日時、phpファイル自体の更新日時を文字列として結合してます。
こうすることで、参照しているデータベースのデータが変更されたときも、phpファイルを修正したときも、Etagが変わるようにできるわけです。
また、リクエストされたURIを入れておくことにより、同じ最終更新日時がたまたま同じになってしまったときも違うEtagを出力することができます(違うURIで同じEtagを出してはいけないわけではないですが、少し気持悪いので)

mixed $time

2つめは、ドキュメントの最終更新日時を渡します。etagCheck()はこれを元に’Last-Modified’ヘッダを出力します。
日時のフォーマットはunixtimeを使用するか、parse_http_date()またはstrtotime()で解釈できるものを使用する必要があります。DBからのISO形式(date()でいうところの’Y-m-d H:i:s’)だとstrtotime()でちゃんと変換されるので、大丈夫です。

クラス関数

(php4は全部publicですけど)publicなクラス関数はひとつだけです。

boolean etagCheck()

etagCheck()関数は、クライアントから送信されたIf-None-Matchと生成されたEtag、クライアントから送信されたIf-Modified-Sinceと生成されたLast-Modifiedをそれぞれ比較し、更新がないと判断したら ‘HTTP/1.1 304 Not Modified’ ヘッダを出力し、trueを返します。
クライアントがはじめてリクエストしたか、ドキュメントが更新されたと判断した場合は、EtagおよびLast-Modifiedヘッダを出力し、falseを返します。
etagCheck()関数がtrueを返した場合、phpスクリプトは続いてドキュメントを出力してはいけません。逆にfalseを返した場合は、通常通りドキュメントを出力する必要があります。

おしまい

どうでしょう、使えますか?
俺はいくつかのシステムにこの方法で組み込みました。やはりトラフィックの節約になるのと、アクセスログに304がちゃんとつくので、お客さんにはけっこう良い感触でしたよ。特に見にくる人がF5を連打するようなタイプのサイトだったので、トラフィックとhttpdの数の節約に効果があったようです。

と、いいつつこのWordPressにはめんどくさいので組み込んでませんケド。

関連記事

1個のトラックバック

  1. PHPWalker さんからのコメント 2005 年 5 月 23 日, 7:36 PM

    Etag送って304

    phpでEtag処理

    これを書いてるおっさんに去年「これ使え。ええことあるで」ともらったEtag処理クラス。
    今でも便利につかわせてもらってます。
    ここを読みに来る人もどうでっか?っつうことで紹介しておきま…