at posts/single.html

ETag を使ってキャッシュ制御(未解決)

ってことで、ページの内容(ログイン/ログアウトのナビゲーション)が違うけど、日記の内容は同じ (Last-Modified が同じ) 場合にどうすればいいか。 Web を調べていたら「ETag」というものが使えそうだと気がついた。 ETag について Web ではあまり情報が見つからなかった(@ITHTTP入門Studying HTTPくらい?)。 要はそのページを一意に特定するための ID みたいなもので、キャッシュ制御に利用するらしい。

キャッシュなら「Last-Modified」と「If-Modified-Since」の組み合わせだけでいいじゃん、と思っちゃうけど、そうでもないみたい。 例えば、1つの URL に対して複数のリソース(英語のページと日本語のページなど)が存在する場合がそう。 もう少し具体的にいうと、 http://example.com/index.html という URL があったとして、日本語版の Web ブラウザでアクセスすると日本語のページが、英語版の Web ブラウザでアクセスすると英語のページが返ってくるようになっていた場合*1、最終更新日は同じでも言語によってページの内容が違うことになる。 んで、そういった場合でも、それぞれに違う ETag を付けておけば、 Web ブラウザ側でどっちのキャッシュを持っているかを明示できるって訳。

同じように、ログイン前後のページに違う ETag を付けておけば、日記の更新日時が同じでもキャッシュ制御が上手くいくんじゃないかと期待。

試してみる(事前準備)

あれこれ考えるよりも、まずは試してみる。 tDiary の index.rb を見てみると、ラッキーなことに既に「ETag testing code」が書かれていた。 ソースはコメントアウトされていたので、有効にしてみる。

@@ -66,7 +66,7 @@
                head['Last-Modified'] = CGI::rfc1123_date( tdiary.last_modified )

                # ETag testing code
-               # require 'md5'
+               require 'md5'
                # head['ETag'] = MD5::md5( body )

                if /HEAD/i !~ @cgi.request_method then
@@ -82,6 +82,7 @@
                                head['Cache-Control'] = 'no-cache'
                        end
                        head['cookie'] = tdiary.cookies if tdiary.cookies.size > 0
+                       head['ETag'] = %Q("#{MD5::md5( body )}")
                        print @cgi.header( head )
                        print body

日記本文のハッシュ値 (MD5) が ETag の値になっている。 元のソースでは body の生成前に ETag を作っていたので、これを body 生成後に移動。 また、HTTP の仕様書では、 ETag は quoted-string と定義されているので、前後を「"」で囲むようにした。 準備ができたので、実際にデータを取ってみる。

トップページの表示(ログイン後)

まずはログイン後のトップページから。

GET /sample/tdiary-auth/ HTTP/1.1
Host: www.machu.jp
(略)
Cookie: _session_id=9b0f837d027031711733eae4524d7479
HTTP/1.x 200 OK
Date: Sun, 27 Aug 2006 03:37:38 GMT
Server: Apache/1.3.37 (Unix)
Cache-Control: no-cache
Etag: "a436b57f1f146309a6fd3989c37ae0d0"
Pragma: no-cache
Vary: User-Agent
Set-Cookie: _session_id=9b0f837d027031711733eae4524d7479; path=/sample/tdiary-auth
Last-Modified: Wed, 21 Jun 2006 07:00:25 GMT
(略)

Last-Modified だけでなく、 Etag が返されるようになっている。

ログアウト

ログアウトボタンをクリックした場合のヘッダ。 先ほどと同じく、ここは重要じゃない。

GET /sample/tdiary-auth/?logout=true HTTP/1.1
Host: www.machu.jp
(略)
Cookie: _session_id=9b0f837d027031711733eae4524d7479
HTTP/1.x 200 OK
Date: Sun, 27 Aug 2006 03:38:05 GMT
(略)

この後、自動的にトップページへ戻る。

トップページの表示(ログアウト後)

GET /sample/tdiary-auth/ HTTP/1.1
Host: www.machu.jp
(略)
If-Modified-Since: Wed, 21 Jun 2006 07:00:25 GMT
If-None-Match: "a436b57f1f146309a6fd3989c37ae0d0"

先ほどの ETag を使わない例では「If-Modified-Since」だけだったんだけど、今度は「If-None-Match」が追加されている。 この値は、ログイン後にトップページを表示したときに、サーバから返ってきた「ETag」の値になっている。 『「07:00:25」に更新された「a436b57...」という ETag のキャッシュをもってますよ』とサーバに伝えている。

HTTP/1.x 200 OK
Date: Sun, 27 Aug 2006 03:38:06 GMT
Server: Apache/1.3.37 (Unix)
Cache-Control: no-cache
Etag: "e72a783e48a9d08453d7690b04397a64"
Pragma: no-cache
Vary: User-Agent
Last-Modified: Wed, 21 Jun 2006 07:00:25 GMT
(略)

サーバからのレスポンスも「304 Not Modified」から、「200 OK」へと変わっている。 この時も ETag を返しているけど、ログイン後の値「a436b57...」じゃなくて「e72a783...」になっている。 これは、ナビゲーションのリンクが「ログアウト」から「ログイン」に変わったため。

ってことで、 ETag を使わない場合はログイン中のページがログイン後も表示されていたけど、 ETag を使うことでキャッシュが使われなくなった。

課題(F5リロードで304が返らない)

これでめでたしと思ったら、そんなに甘くなかった。 試しに F5 でリロードしてみる。

GET /sample/tdiary-auth/ HTTP/1.1
Host: www.machu.jp
(略)
If-Modified-Since: Wed, 21 Jun 2006 07:00:25 GMT
If-None-Match: "e72a783e48a9d08453d7690b04397a64"

最終更新日も ETag も変わらないから、本来なら 304 が返るはずだけど…。

HTTP/1.x 200 OK
Date: Sun, 27 Aug 2006 04:33:30 GMT
Server: Apache/1.3.37 (Unix)
Cache-Control: no-cache
Etag: "e72a783e48a9d08453d7690b04397a64"
Pragma: no-cache
Vary: User-Agent
Last-Modified: Wed, 21 Jun 2006 07:00:25 GMT
(略)

なぜか 200 が返ってきてしまう orz 。 これじゃ、キャッシュを無効にした場合と変わらないよ。

ステータスコード 200 と 304 のどっちを返すかは、 tDiary ではなく Web サーバ (Apache) が判断しているっぽい。 うーん。これ以上調べるか、それともここで割り切るか…。

関連する日記