Test::Apache::RewriteRules で mod_rewrite のテストを書こう

YAPC::Asia Tokyo 2010 で LT してきました。以下はその資料(に少し説明を追加したもの)です。

mod_rewrite

  • 正規表現によるURL書き換えモジュール
  • スイス製アーミーナイフ / 黒魔術
  • まだ Apache 使ってますよね?
  • reverse proxy とか…

はてなの mod_rewrite 活用事例

  • ほぼ reverse proxy
  • URLにより用途別のbackendに振り分ける
    • 用途によりbackendを分けリソース効率化
    • 特定のアクセスをキャッシュサーバーに振る
  • URL加工
    • Squidにキャッシュさせたいが同一URLで異なるコンテンツを返す場合がある
    • →クエリに情報を付加する
  • BAN!

便利な半面…

  • 増える!
$ cat jp.www.proxy.apache.conf | grep Rewrite | wc -l
     179
  • テストしづらい! → 一行加えるのも恐ろしい…

Test::Apache::RewriteRules

そこで、 Test::Apache::RewriteRules 。

はてな社内製テストモジュール (id:wakabatan作成) で、Apache の RewriteRule の動作をテストできます。

こちらで公開中です → http://github.com/hatena/perl5-test-apache-rewriterules

しくみ

  1. Rewrite、Redirect など path 関係のディレクティブだけを残した apache.conf を作る
  2. バックエンドを登録する
  3. Apache を起動する
  4. 実際に HTTP 要求を投げて、どの URL にアクセスがあったかを調べる

環境設定

  1. Apache2 を入れる
  2. mod_ssl など必要モジュールを入れる

SYNOPSIS

こんな apache.conf があったとして

RewriteRule /foo/(.*)  http://%{ENV:ReverseProxyedHost1}/$1 [P,L]
RewriteRule /bar/(.*)  http://%{ENV:ReverseProxyedHost2}/$1 [P,L]
RewriteRule /hoge/(.*) http://external.test/$1 [R,L]

こんなテストが書けます。

use Test::Apache::RewriteRules;
use Path::Class qw/file/;
my $apache = Test::Apache::RewriteRules->new;

$apache->add_backend(name => 'ReverseProxyedHost1');
$apache->add_backend(name => 'ReverseProxyedHost2');

$apache->rewrite_conf_f(file('apache.rewrite.conf'));

$apache->start_apache;

$apache->is_host_path('/foo/a', 'ReverseProxyedHost1', '/a');
$apache->is_host_path('/bar/b', 'ReverseProxyedHost2', '/b');,
$apache->is_redirect('/hoge/z', 'http://external.test/z');

$apache->stop_apache;

設定ファイルの書き換え

通常、Include ディレクティブなどでフルパスを指定しているので、設定ファイルをコピーして書き換えます。

文字列 or 正規表現

↓

文字列 or コード参照

書き換え例

my $copied_conf = $apache->copy_conf_as_f($org_file, [
    "foo"         => "bar",
    "hoge"        => sub { "fuga" },
    qr/(foo|bar)/ => "baz$1",
]);
  • VirtualHost ã‚„ CustomLog など関係ないディレクティブも全部消します
  • RewriteRule だけの conf ファイルだと簡単です

apache.conf をセット

$apache->rewrite_conf_f($copied_conf);

Apache の起動に必要なディレクティブなどはモジュールが勝手に用意します。

バックエンドの登録

リバースプロキシ先のバックエンドを登録します。

$apache->add_backend(name => 'BackendMainVIP');
$apache->add_backend(name => 'BackendBotVIP');
$apache->add_backend(name => 'SquidVIP');

RewriteRule で %{ENV:BackendMainVIP} のように環境変数を使える
実際の apache.conf でこのような環境変数を使って記述していない時は、 copy_conf_as_f で書き換える

Apache の起動

$apache->start_apache;

テスト (1) is_host_path

$apache->is_host_path('/' => 'BackendMainVIP', '/');
  • is_host_path .. Rewrite 後の host, path のテスト
    1. アクセスする path
    2. バックエンド名
    3. rewrite 後の path

テスト (2) is_redirect

$apache->is_redirect('/foo' => 'http://example.com/bar');
  • is_redirect .. リダイレクトのテスト
    1. アクセスする path
    2. リダイレクト先の URL

Test::Apache::RewriteRules::ClientEnvs

特定のクライアント環境を再現するブロックを提供します。

with_docomo_browser {
    $apache->is_redirect('/n/' => 'http://n/mobile/?&guid=on');
};

with_request_method {
    $apache->is_host_path('/' => 'BackendMainVIP', '/');
} 'POST';

現在サポートしている環境

  • with_request_method
    • HTTP 要求メソッドを指定
  • with_http_cookie
  • with_http_header_field
    • HTTP ヘッダフィールドを追加
  • with_*_browser

Apache の停止

$apache->stop_apache;
# (DESTROY でも呼ばれますが)

まとめ

Test::Apache::RewriteRules で何百行もあるRewriteRuleに自信を持って一行追加できます!

今後も、はてな社内製の便利モジュールをもっと外に出していこうと思います。