PerlでAtomPubサーバを作ろう!

第3回 AtomPubをより効果的に ─ 認証・キャッシュなど

この記事を読むのに必要な時間:およそ 6 分

キャッシュ・バージョンチェック

AtomPubにはキャッシュやバージョンチェックが定義されています。クライアントは,過去に取得したりソースに対して変更の有無を問い合わせ,変更されていなければキャッシュを参照します。また,サーバは,クライアントが古いバージョンのリソースに対して変更を加えたときに,そのことを検出することができます。

キャッシュとバージョンチェックの仕組み

サーバがこれらの機能を実装するかどうかは自由です。実装すると,次の利点が得られます。

  • メンバリソースがGETされるとき,リソースが変更されていなければ304 Not Modifiedを返すことができる。リソース本体を返さないので,帯域幅が節約される。
  • メンバリソースがPUTされるとき(更新されようとしているとき)⁠変更が加えられたバージョンをチェックすることができる。古いバージョンに対して変更が加えられたときには,更新を拒否できる(412 Precondition Failedエラーが返る)⁠

これらを実現するために,ETagあるいはLast-ModifiedというHTTPヘッダが用いられます。まず,ETagについて説明します。

サーバは,メンバリソースを返すときにETagヘッダを付与することができます。ETagはリソースのバージョンを区別するための識別子になります。

クライアントは,メンバリソースをGETするときに,If-None-MatchヘッダにETag値を設定します。サーバは,値が一致しなければリソースが更新されたと判断して,リソース本体を返します。一致すれば,更新されていないとみなし,304 Not Modifiedを返してキャッシュを参照するように促します。

クライアントがメンバリソースをPUTするときには,ETag値をIf-Matchヘッダに設定します。サーバは,値が一致すれば更新を受け入れます。一致しなければ,古いバージョンに対して変更を加えたとみなし,更新を拒否します。

Last-Modifiedはリソースの最終更新日時を表します。それ以外はETagとほぼ同様に使われます。If-None-Match/If-Matchの代わりにIf-Modified-Since/If-Unmodified-Sinceが使われます。

ただし,Last-Modifiedは秒を単位とするため,1秒以内に連続して更新が行われた場合にはバージョンを区別することができません。このため,ETagのほうが安全と言えます。

キャッシュとバージョンチェックを実装

Catalyst::Controller::Atompubでキャッシュやバージョン管理を実現するには,コレクションコントローラのfind_versionメソッドをオーバライドし,ETagあるいはLast-Modifiedを返すように実装します。

第1回で作成したエントリコレクションにfind_versionを実装し,ETagを返すようにしてみます。

まず,テーブルにetagカラムを追加します。

MyBlog % sqlite3 test.db
sqlite> ALTER TABLE entries ADD COLUMN etag TEXT;

リソースを追加あるいは更新するときにETagを計算し,テーブルに格納しておきます。ETagの値はリソースのハッシュ値とします。

メンバ追加時にETagを計算(lib/MyBlog/Controller/EntryCollection.pm)

use Digest::MD5 qw(md5_hex);
sub create_entry :Atompub(create) {

    # 省略...

    # entriesテーブルにエントリとメタデータを格納する
    $c->model('DBIC::Entries')->update_or_create({
        edited => $edited,
        uri    => $uri,
        xml    => $entry->as_xml,
        etag   => md5_hex($entry->as_xml),
    });

    # 成功したらtrueを返す
    return 1;
}

メンバ更新時にETag を計算(lib/MyBlog/Controller/EntryCollection.pm)⁠

sub update_entry :Atompub(update) {

    # 省略...

    # entriesテーブルからエントリを検索し,更新する
    $c->model('DBIC::Entries')->search({ uri => $uri })->update({
        edited => $edited,
        xml    => $entry->as_xml,
        etag   => md5_hex($entry->as_xml),
    });

    # 成功したらtrueを返す
    return 1;
}

find_versionメソッドを実装します。

find_version(lib/MyBlog/Controller/EntryCollection.pm)

sub find_version {
     # $uriはリクエストされた URI
     my($self, $c, $uri) = @_;

     # $uriに対応するリソースを取得する(リソースが存在しなければ空配列を返す)
     my $rs = $c->model('DBIC::Entries')->find({ uri => $uri }) || return;

     # ETagをハッシュとして返す
     return (etag => $rs->etag);

     # ETagとLast-Modifiedをハッシュとして返すこともできる
     #return (etag => $rs->etag, last_modified => $rs->last_modified);
}

URI に対応するリソースを検索し,先ほど格納したETag値を返します。あとはスーパークラスが適切に処理します。

メディアリソースについても同様に実装します。

著者プロフィール

井上武(いのうえたける)

NTTに入社後,未来ねっと研究所でマルチキャストやモバイルIPなどのネットワーク技術の研究開発に取り組んでいたが,最近はWebアーキテクチャに関する仕事をしている。

URLhttp://teahut.sakura.ne.jp/