URLを書き換えろ!
Apache Rewrite 機能の応用

Rewrite 機能とは

Rewrite 機能とは、Apache 1.2 で付け加わった機能であり、アクセスしたURLを正規表現を使って書き換えて処理する機能である。だから、既存機能としては Alias や Redirect と類似するが、さらに柔軟で、さまざまな使い道がある。違いを簡単に述べよう。

Rewrite, Alias, Redirect の比較

Alias 機能と ScriptAlias 機能
Alias はディレクトリ単位で、アクセスURLを DOCUMENT_ROOT から外れたディレクトリにマップする。CGIディレクトリに対して常識的に使われる ScriptAlias 指示子は、この Alias 指示子の機能に加え、そのディレクトリが実行可能なファイルを含み、その実行を許可するマークを付け加えている。逆に言えば、Alias 機能はそのようなマークアップを含まずに、単純に DOCUMENT_ROOT 外のディレクトリを DOCUMENT_ROOT 以下のディレクトリとしてマージする。だから、Alias 機能ではそのサーバ外部のディレクトリを扱うことはできない。ブラウザの上からは、Alias によってマップされたディレクトリなのか、それとも真のディレクトリ構造に含まれるものなのかは区別できない。
Redirect 機能
Redirect 機能は HTTP の「301 Moved Permanently」レスポンスコードを発行し、ブラウザに「要求URLは移転した」ことを伝える。だから、移転先URLを更に処理するのはブラウザの役目であり、通常のブラウザは自動的にこの処理を行う。それゆえ、ブラウザ側で認識する「そのページのURL」は移転先URLとなる。この違いはサイトの移転などの事情では、大変役立つわけである。つまり、一旦 Redirect 機能で転送されたページは、あたかも最初からその移転先ページを要求したのと同じことになり、再度同一ページを要求したとしても、転送元から改めて請求しなおすことはない。「今後は転送先URLを使って下さい!」ということを暗黙のうちに伝えることになるのである。ブラウザが再度処理をしなおすために、同一サーバに転送先がなければならない、というような制限はまったくない。
プロトコルのレベルでは以下のようなシーケンスで実行される。
#  http://berio.kobe-du.ac.jp/sugiura/index.htm を取得しようとする
GET http://berio.kobe-du.ac.jp/sugiura/index.htm HTTP/1.0

HTTP/1.1 301 Moved Permanently ←移転を示すレスポンスコード
Date: Sun, 29 Jun 2003 18:18:48 GMT
Server: Apache/1.3.x ....
Location: http://berio.kobe-du.ac.jp/sugiura/index.shtml
#  移転先が Location ヘッダに書かれている
Connection: close
Content-Type: text/html

#  Location ヘッダに書かれている移転先に再度アクセス
GET http://berio.kobe-du.ac.jp/sugiura/index.shtml HTTP/1.0

HTTP/1.1 200 OK ←成功を示すレスポンスコード
Date: Sun, 29 Jun 2003 18:18:57 GMT
Server: Apache/1.3.x ....
Connection: close
Content-Type: text/html

実際のファイルの内容................
Rewrite 機能
さて、本題の Rewrite 機能である。これは Alias 機能と違い、ファイル単位での処理が可能で、転送先が同一サーバになければならないような制限もない。また、Redirect 機能と違い、すべて Apache の内部で処理されるために、ブラウザに対しては要求URLが完全に存在するかのように振舞う。それに加えて、次の便利な機能が追加されている。
  1. 転送元→転送先への置換は、正規表現を使って指定できる。だから、1つのルールを書いておけば、複数のURLを複数の転送先にマッピングできる。Redirect では実URLしか書けないので、大量の転送マッピングを書く必要があるが、Rewrite を使えば簡潔に書ける。とはいえ、正規表現を理解する必要があるが、これは現在の管理者にとっては常識の部類に入っているので、改めて解説はしない。もし読者が正規表現について知らなければ、いい機会なので勉強するように。実にさまざまな分野で利用されているものなので、あなたのスキルを確実に向上させる。
  2. 何らかの環境変数(というか、この場合にはサーバ内部の話なので、「サーバ変数」だが...)を参照して、場合分けをすることができる。つまり、接続元やブラウザ種別に応じて、振り分けをすることもできるのである。
  3. さらに多重に置換ルールを複合させて、複雑な置換をさせることもできる。少々プログラミング言語的な(とは言っても sed が「言語」である程度のものだが...)処理をさせることさえ可能である。

Rewrite 機能の指定子

このような機能を実現するために、次の指定子がある。基本的には /etc/httpd/conf/http.conf などの Apache 設定ファイルに記述する。一部の指定子は .htaccess にも書ける。書く位置は、設定ファイルのトップレベル、<VirtualHost>ブロック内部、<Directory>ブロック内部などに書くことができ、ブロック内部に書けば、通用範囲はそのブロック内部に制限される。

なお、.htaccess に書けるのは RewriteEngine, RewriteBase, RewriteCond, RewriteRule だけで、ロギングに関する指定子などは書けないし、色々と注意すべきハマリ点がある。だから、.htaccess に書く時の注意事項は章を改めて、次のページに書くので、とりあえずここで概説として理解した後に、「.htaccess に書く場合の注意」を読まれれば完璧である。

RewriteEngine On|Off
これは Rewrite 機能自体のオン・オフを制御する。


RewriteLog ファイル名
Rewrite 機能のログを取るためのファイルを指定する。トップレベルと<VirtualHost>ブロックにしか書けない。


RewriteLogLevel 数値
Rewrite 機能のログのログレベルを 0〜9 の数値で指定する。トップレベルと<VirtualHost>ブロックにしか書けない。


RewriteBase ベースとなるURL
これはディレクトリごとに変換のためのベースとなるURLを補う。<Directory>ブロックに書く場合、もし Alias などで物理ディレクトリとの対応関係が変わっている場合に、これで間違いないように正しく指定する。


RewriteCond %{サーバ変数名} 正規表現パターン
これはアクセスごとに設定され、CGIの中で「環境変数」として取得される、「サーバ変数」を参照し、それが正規表現パターンと一致していれば、次行の変換(RewriteRule や再度の RewriteCond)を引き続き実行する、という機能を持つ。RewriteCond は他に「< >」などを使った文字比較もできる。たとえば時間を見て、7時から19時までとそれ以外で振り分けるファイルを返るためには次のようにする。
RewriteCond  %{TIME_HOUR}%{TIME_MIN} > 0700 # 条件成立ならば次へチェーン
RewriteCond  %{TIME_HOUR}%{TIME_MIN} < 1900 # 条件成立ならば次へチェーン
RewriteRule  ^/index\.htm$  /daytime.htm    # なので上記条件2つとも成立の場合のみ
RewriteRule  ^/index\.htm$  /night.htm      # 条件が1つでも不成立の場合のみ 
RewriteRule 一致パターン 置換パターン フラグ
さて、これが実際の置換パターンを指定する指示子である。正規表現は awk や Perl 風であり、メタ文字として「+」が使え、置換パターンで参照するためには一致パターン内部で「()」で囲み、置換パターン内で「$1」などで参照する。あとは通常の正規表現である。また、第3引数として「その後の処理」を表す「フラグ」を取る。このフラグによってかなり複雑な処理を指定できる。フラグの有用なものには、次のものがある(すべてではない...)。

(デフォルト:フラグ指定無し)
次のルールへ進む。
[C]
次の規則にチェーンする。今の変換パターンと一致し、変換がなされた時だけに次のルールを適用する。要するに Perl 風に書けば、
RewriteRule ^/subdir/(.*)  /$1 [C]  # if( $dir =~ m|^/subdir/(.*)| ) {
                                    #     $dir = "/$1";      
RewriteRule ^/([^/]*)/pub/(.*) /pub/$1/$2 # if( $dir =~ m|^/([^/]*)/pub/(.*)| ) {
                                                $dir = "/pub/$1/$2";
                                    #       }                                            
                                    # }
RewriteRule ^/test/(.*) /pub/tmp/$1 # if( $dir =~ m|^/test/(.*)| ) {
の要領になる。つまり、複合条件を制御できる。
[L]
これでRewrite 機能による変換を打ち切ることを示す。
[N]
変換ルールの適用を最初からやり直す。しかし、現在変換済みのURLを対象として行くので、実質上ループをつくることができる。
                                # while( 1 ) {
RewriteRule sugiura  sug [N]    #    if( $dir =~ m|sugiura| ) {
                                #         $dir =~ s|sugiura|sug|;
                                #         next;
                                #    } else {
RewriteRule (.*) $1 [L]         #         last;
                                #    }
                                #}
の場合ならば、すべての「sugiura」という単語を「sug」に置き換える。つまり、「/sugiura/sugiura/sugiura.htm」は「sug/sug/sug.htm」になる。
[PT]
「Path Through」させる。つまり、[L]と似ているが、Rewrite による変換を終りにして、Rewrite 以外の変換(ScriptAliasなど)に渡す。CGI の場合には、ScriptAlias によって更にAlias による変換を必要とするので、これを指定するべきである。ただ、将来のバージョンではサポートされない方向に進むようである。
[R]
Redirect させる。つまり、変換されたURLを「Redirect」するものとして、「HTTP/1.1 302 Moved Temporary」のレザルトコードと、Loacation ヘッダに変換されたURLがセットされたレスポンスを返す。このフラグは結果として変換結果を確定し、[L]フラグを指定したのと同様に、以降のルールの適用をキャンセルする。
[F]
FORBIDDEN を返す。つまり、アクセスが禁止されていることを示すために、「HTTP/1.1 403 FORBIDDEN」のレスポンスを返す。このフラグは結果として変換結果を確定し、[L]フラグを指定したのと同様に、以降のルールの適用をキャンセルする。


が、現実的にはそれほど複雑な変換が必要になることは単純なサイトでは少ないだろう。この機能が大活躍するようなサイトは次のようなものであろう。

  1. アクセス効率を上げるために複数のサーバを実際には立てているが、窓口は1つにするケース。たとえば企業サイトで、統一的なURLとして「http://www.社名.com/部局」のように割り振るが、実は部局ごとにサーバを立てており、それらを窓口WWWサーバがアクセスURLから各部局サーバに割り振る。
  2. ミラーサイトがいくつかあり、アクセス者のホスト名に従って「近そうな」ミラーサイトにリダイレクトする場合。
  3. 人気サイトで、今まで多くのアクセス者があり、多くのページにディープリンク(トップページ以外のリンク)がされており、多くの検索サイトに登録されている場合に、大幅なディレクトリ構造の見直しをした。古いURLでも新しいURLでもアクセスできるようにする。

Rewrite 機能の実例

準備として、まず apache 設定ファイル /etc/httpd/conf/httpd.conf などの中に、

LoadModule rewrite_module     modules/mod_rewrite.so
.........
AddModule mod_rewrite.c

の2行があり、コメントアウトされていないことを確認する。Apache では「拡張モジュール」によってさまざまな興味深い機能が実装されており、それらの機能を実際に使うには、設定ファイルでロードしておく必要があるのである。

単純なリダイレクトをするのには、次のようにするだけである。単純リダイレクトでも、すべてサーバ内部で処理され、それが結果としてCGIで環境変数を参照するとしても、環境変数 REQUEST_URI と SCRIPT_NAME が一致しないことでそれと知られるだけである。つまり、REQUEST_URI は要求のままだが、SCRIPT_NAME は実際に返されるCGIのパス名が入る。また、サーバログは実際にアクセスされたファイル名が入る。

<Directory "/var/www/html/">
    Options IncludeNoExec SymLinksIfOwnerMatch
    AllowOverride None
    RewriteEngine On
    RewriteBase /var/www/html
    RewriteRule index\.html index.shtml [L]
    RewriteRule index\.htm  index.shtml [L]
</Directory>

これで、/var/www/html/index.html によるアクセスでも、/var/www/html/index.htm によるアクセスでも、SSIを使った /var/www/html/index.shtml に置換される。

この時、これはディレクトリ内部での置換になるので、どうやら一種のリンク参照のような扱いになり、「SymLinksIfOwnerMatch」か「FollowSymLinks」が指定されていないと正しく転送先を見つけられないようである。ディレクトリ内部ではこの指定が必要になるが、それ以前で置換がなされてしまう <VirtualHost>ブロック内、トップレベルに記述する場合には、このようなことを気にする必要はないようである。

しかし、上記の例は冗長である。正規表現を使えば次のような書き方もできる。

<Directory "/var/www/html/">
    Options IncludesNoExec SymLinksIfOwnerMatch
    AllowOverride None
    RewriteEngine On
    RewriteBase /var/www/html
    RewriteRule index\.html? index.shtml [L]
</Directory>

つまり、「?」は先行する要素の0回もしくは1回の繰り返しを表す正規表現メタ文字であり、この場合には「l」があってもなくても良いことを示す。また、正規表現では「.」はメタ文字であり、任意の1文字とマッチする特殊な文字である。そのため、

    RewriteRule index.html? index.shtml [L]

と書いても、「index.htm」とちゃんとマッチするが、「index-htm」ともマッチしてしまう。そういう不測の事態を招かないためにも、ただしく処理するように、「\」でメタ文字をエスケープしておく。

この置換を使えば、クエリから実際の処理をするCGIを振り分けることさえ可能である。やや古い PATH_INFO 風の書き方の例でやってみよう。

http://maborosi.kobe-du.ac.jp/cgi-bin/mycgi.cgi?key=myID&mode=owner

現在では上記のようにクエリ文字列として、追加情報を渡すのが普通であるが、かつては PATH_INFO として、次のようにする方法が取られていた。

http://maborosi.kobe-du.ac.jp/cgi-bin/mycgi.cgi/myID/owner

このURLの場合に、mode が owner ならば、この処理を /cgi-bin/owner.cgiに任せることもできるのである。これは次の通り。

<Directory "/var/www/cgi-bin/">
    Options ExecCgi SymLinksIfOwnerMatch
    AllowOverride None
    RewriteEngine On
    # ScriptAliasの効果のため /var/www/html/cgi-bin だと思われるので必須!
    RewriteBase /cgi-bin   
    RewriteRule ^mycgi\.cgi/([^/]*)/owner$ owner.cgi?owner=$1 [L]
    RewriteRule ^mycgi\.cgi/([^/]*)$ mycgi.cgi?key=$1 [L]
</Directory>

これは次のように置換する。

mycgi.cgi/mykey/owner-> owner.cgi?owner=mykey
mycgi.cgi/mykey-> mycgi.cgi?key=mykey
mycgi.cgi-> mycgi.cgi




copyright by K.Sugiura, 1996-2006