mod_rewrite マニアックス

カテゴリ
ブックマーク数
このエントリーを含むはてなブックマーク はてなブックマーク - mod_rewrite マニアックス
このエントリーをはてなブックマークに追加

こんにちは。開発部の池邉です。

既に色んなところで発表していますが、ライブドアではWebサーバとして殆んどのサービスでApacheを使用しています。
Apache の特徴として、モジュールによる機能の追加、挙動の制御があります。その中でもよく利用されていながら、深く使っていくとハマりどころや謎の機能の多いモジュールとして mod_rewrite があります。Apacheの公式サイトでも以下のように書かれている事からも、本家でもその点については認めているという事でしょう。
今回はそんな mod_rewrite のちょっとマニアックな拡張方法について紹介したいと思います。
Despite the tons of examples and docs, mod_rewrite is voodoo. Damned cool voodoo, but still voodoo.

変数を利用したプログラム的な設定

ライブドアでは携帯電話等の特定の端末を制御するため、時間指定で特定のページを見せるため等の理由で mod_rewrite を多用しています。mod_rewrite ではサーバ内部の状態、クライアントから送信されるヘッダ等の情報を変数として扱い、プログラム的な制御構造を持った設定を記述する事が出来ます。

たとえば、ある決められた日付だけページを差し替える場合の RewriteRule は以下のようになります。この設定では、2010年の4月1日のエイプリルフールだけ別のファイルを見せる事が出来ます。
 RewriteEngine On
 RewriteCond %{TIME_YEAR}%{TIME_MON}%{TIME_DAY} =20100401
 RewriteRule ^/$ /path/to/april_fool.html [L]
mod_rewrite で参照出来る変数の種類は非常に多く、大抵の事が出来るのですが、その結果として httpd.conf は巨大になり、テキストファイルながら 数10KB程度になってしまいます。mod_rewrite では、Apache のディレクティブの記法でプログラミング的な制御構造を表現するため、プログラミング言語に比べてどうしても冗長で直感的では無い記述になってしまいます。ある程度複雑な条件になってくると、不自由な mod_rewrite のディレクティブでは無く、まともな言語でプログラムを書いた方が楽に思えてきます。

独自のプログラムでの制御

mod_rewrite では独自のプログラムで URL の変換を行う仕組みもあります。RewriteMap の MapType で prg を指定する事で、外部プログラムを使用する事が出来ます。任意の言語でプログラミングが出来るため、一見便利に見えますが、RewriteLock ディレクティブによるロックが必要だったり、外部プロセスなので遅いというような問題があり、あまり使われていないのが実際のところです。
 RewriteLock /tmp/rewritelock
 # RewriteMap で使用する Map の種類とプログラムまでのパスを指定
 RewriteMap example prg:/path/to/example.pl
 RewriteRule ^/(.*) ${example:$1}

#!/usr/bin/env perl
use strict;

# 標準入力から読み込み、値を書き換えて標準出力に出力する。
$| = 1;
while(my $line = ) {
    chomp $line;
    my $path = "/". $line. "-foo";
    print $path, "\n";
}

C言語での拡張

RewriteMap のMapType に指定出来る値は以下の5種類です。この中で int は Internal Function の略で、内部的な関数を呼びだします。
  • txt
  • rnd
  • dbm
  • int
  • prg
公式ドキュメントでは以下のように記述されており、独自の関数は作れない事になっています。しかし、ソースコードを読んでみると、独自の関数を追加する仕組みが仕込まれている事が分かります。
MapType: int, MapSource: Internal Apache function
Here, the source is an internal Apache function. Currently you cannot create your own, but the following functions already exist:
mod_rewrite に独自の RewriteMap 用の関数を追加するためには mod_rewrite から公開されている ap_register_rewrite_mapfunc という関数を使用して Apache モジュールを書きます。
外部プログラムと比較しての利点としては、Apache のライフサイクルの中で実行されるため、外部プログラムと比べて高速であり、request_rec 構造体を扱えるのでHTTPヘッダや Cookie のような HTTP 固有の情報を容易に扱う事が出来ます。値の書き換えだけを実装し、肝心の URL 変換部分は mod_rewrite にまかせてしまうため、Apache モジュールとしては非常に少ない行数で済むのも利点です。
#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "ap_config.h"
#include "apr_strings.h"
#include "mod_rewrite.h"

static char *rewrite_myfunc_handler(request_rec *r, char *key) {
    /* 末尾に "-foo" という文字列を連結 */
    return apr_pstrcat(r->pool, key, "-foo", NULL);
}

static int rewrite_myfunc_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) {
    APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register;
    map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc);
    if (map_pfn_register) {
        /* 関数を登録 */
        map_pfn_register("myfunc", rewrite_myfunc_handler);
    }
    return OK;
}

static void rewrite_myfunc_register_hooks(apr_pool_t *p) {
    ap_hook_pre_config(rewrite_myfunc_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA rewrite_myfunc_module = {
    STANDARD20_MODULE_STUFF, 
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    NULL,                  /* table of config file commands       */
    rewrite_myfunc_register_hooks  /* register hooks                      */
};

最後に

今回は mod_rewrite の拡張方法として、C言語での拡張方法を紹介しました。少し前に mod_rewirte のソースを読んでいて、拡張が出来るという事を発見したのが嬉しかったのでエントリーにしてみました。
いつも使い慣れてるソフトウェアも、ちゃんとソースコードを読むのは大事だなとあらためて思った次第です。

それでは今年もよろしくお願いします。