mod_rewrite活用テクニック

出典
Software Design 2007年 09月号
テスト環境
CentOS5 テスト用最小インストール手順参照。
apacheバージョン
2.2系

mod_rewriteとは

  • URL書き換えモジュール。
  • 正規表現で書き換え

正規表現いろいろ

  • この辺りを参考に。
  • ? の使い方
    直前にある部分正規表現、もしくは空文字列にマッチします。
    
    すなわち
    hoohoohoo
    
    の最初の"oo"を取りたい場合は、
    h(.+)hoo
    
    だと、"oohoo"になる。こうではなくて、
    h(.+?)oo
    
    になりまっす。知らなんだ。

mod_rewriteの導入

ビルド&インストール

CentOS5の標準パッケージでは動的に組み込まれている。

# httpd -M 2>&1 | grep -i rewrite
 rewrite_module (shared)

ソースからビルドした場合はapxsを使う。

# cd /usr/local/src/httpd-2.2.4/
# /(apxsへのパス)/apxs -i -a -c modules/mappers/mod_rewrite.c

httpd.confでロードされていることを確認。

LoadModule rewrite_module     modules/mod_rewrite.so

設定

基本

RewriteEngine On
RewriteCond 〜
RewriteRule 〜
RewriteEngine
mod_rewriteの有効化/無効化。例えば/var/www/html/fooでは有効、その下の/var/www/html/foo/barでは無効化するなら以下のようになる。
<Directory /var/www/html/foo>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.+)$ /cgi-bin/notfound.cgi/$1 [L,QSA]
</Directory>
<Directory /var/www/html/foo/var>
    RewriteEngine Off
</Directory>
/var/www/htmlをDocumentRootとすると、http://〜/foo/に存在しないファイル・ディレクトリにアクセスした場合、/cgi-bin/notfound.cgiが処理される。かつ、/cgi-bin/notfound.cgi/foo/アクセスしたファイル・ディレクトリ名にアクセスしたのと同じことになる。 1 逆にhttp://〜/foo/bar/に存在しないファイル・ディレクトリにアクセスした場合は、rewriteされないので404エラー画面が表示される。
RewriteCond
rewriteする条件を指定。例えば携帯からアクセスした場合に/mobile/に振り分ける場合
RewriteCond %{REQUEST_URI} !^/mobile/
RewriteCond %{HTTP_USER_AGENT} ^DoCoMo      [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^J-PHONE     [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^Vodafone    [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^MOT-        [NC,OR]
RewriteCond %{HTTP_X_JPHONE_MSNAME} .*      [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^KDDI-       [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^UP¥.Browser [NC]
RewriteRule ^/(.*)$ /mobile/$1 [QSA,L]
User-Agentで携帯かどうかを判断。NCはCase-Insensitive、ORは次のRewriteCondとOR条件でつなげるオプション。
キモは1行目の無限ループ対応処理。
携帯から/productにアクセス。
↓
/mobile/producにRewriteRule。再度ループに入る。
↓
1行目に合致するのでRewriteRuleされない。
指定できる環境変数はこの辺を参照。
RewriteRule
書き換えのルールを指定。
RewriteRule 書き換え前URL 書き換え後URL フラグ
書き換え前URLは、RewriteRuleをどこに書くかによって変わるので注意。例としてhttp://〜/foo/test.htmlにアクセスする場合、以下のパターンでどうなるかを見る。
DocumentRoot "/var/www/html"
<Directory /var/www/html/foo>
    RewriteEngine On
    RewriteRule ^(.+)\.html$ /$1.php [R=301]
</Directory>
この場合、$1にはtestが入るので、http://〜/test.phpに飛ぶ。二つ目の例。
    DocumentRoot "/var/www/html"
    RewriteEngine On
    RewriteRule ^(.+)\.html$ /$1.php [R=301]
上記の場合、$1には"foo/test"が入るので、http://〜/foo/test.phpに飛ぶということ。つまり、サーバ設定コンテキストや!VirtualHostにある場合は、ドキュメントルートからのパス、Directoryディレクティブの中にある場合は、そのディレクトリからの相対パスとなる。 場面ごとにフラグをどう使うかは重要!!!ここを参照。
RewriteBase
書き換えのベースとなるURLを指定する。Aliasなどを使っていてURLとディレクトリ名が対応していない場合などで使用する。例えば、
DocumentRoot /var/www/html
Alias /software /home/product
で、/home/product/.htaccessに
    RewriteEngine On
    RewriteRule ^cf$ cf.cfm
となっている場合に、http://〜/software/cf にアクセスすると /home/product/cf.cfmを見せたい。
/software/cf が /home/product/cf に Aliasされる。
↓
ディレクトリパス部分を取り除いて、"cf"だけにする
↓
cfがcf.cfm に Rewriteされる。
↓
ディレクトリパス部分を元に戻して、"/home/product/cf"にする
↓
http://〜/home/product/cf.cfmにアクセス
となり404エラーとなる。これを回避する為に、
    RewriteEngine On
    RewriteRule ^cf$ cf.cfm
とすると、
/software/cf が /home/product/cf に Aliasされる。
↓
ディレクトリパス部分を取り除いて、"cf"だけにする
↓
cfがcf.cfm に Rewriteされる。
↓
ディレクトリパス部分を元に戻す際に、RewriteBaseをつけて"/software/cf"にする
↓
http://〜/software/cf.cfm にアクセス
↓
/software/cf.cfm が /home/product/cf.cfm に Aliasされる。
となる。つまりRewriteBaseは以下のような場合に.htaccessが存在しているディレクトリがURLではどこに当るのかをapacheに教える。ちとややこしい。
  • alias使ってる時
  • .htaccessで指定している時
RewriteLogRewriteLogLevel
Rewriteの状況をログに記録する。LogLevelは検証だと3程度、本番ではOFFにすべし。
RewriteLog logs/rewrite_log
RewriteLogLevel 3
<Directory /var/www/html/foo>
    RewriteEngine On
    RewriteRule ^(.+)$ /bar/$1 [L,QSA]
</Directory>
http://〜/foo/aaa.html にアクセスするとhttp://〜/bar/aaa.htmlにアクセスしたのと同じになる。でログ。
10.211.55.6 - - [10/Sep/2007:21:56:33 +0900]
 [10.211.55.3/sid#82fa3f0][rid#83ea460/initial] (3) [perdir /var/www/html/foo/] strip per-dir prefix: /var/www/html/foo/aaa.html -> aaa.html
10.211.55.6 - - [10/Sep/2007:21:56:33 +0900]
 [10.211.55.3/sid#82fa3f0][rid#83ea460/initial] (3) [perdir /var/www/html/foo/] applying pattern '^(.+)$' to uri 'aaa.html'
10.211.55.6 - - [10/Sep/2007:21:56:33 +0900]
 [10.211.55.3/sid#82fa3f0][rid#83ea460/initial] (2) [perdir /var/www/html/foo/] rewrite 'aaa.html' -> '/bar/aaa.html'
10.211.55.6 - - [10/Sep/2007:21:56:33 +0900]
 [10.211.55.3/sid#82fa3f0][rid#83ea460/initial] (1) [perdir /var/www/html/foo/] internal redirect with /bar/aaa.html [INTERNAL REDIRECT]

実践編

メンテナンス画面への切り替え

DocumentRoot直下にmainte.htmlがあったら全てのアクセスをそちらに飛ばす。

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/mainte.html -f
RewriteCond %{SCRIPT_FILENAME} !mainte.html
RewriteRule ^.* /mainte.html [L]

でもこれだとクローラーに拾われるかも。302 Moved Temporarilyあたりがよいのかな。

RewriteRule ^.* /mainte.html [R,L]

ちなみにmod_rewriteだと30X系しか吐けないみたいなので、"503 Service Unavailable" あたりで返したい場合は、apacheだけだと無理ということで、この辺を参照。

REST対策

REST = Representational Status Transfer。

  • URLとリソースが結びついている。
  • URLは不変 1リソースにつき1URI。1URIに複数のリソースはだめ。とあるURLの○番目の記事とかだと、永続的に参照できる保証がないのでダメ。
  • URLが実装に依存しない。 特定のプログラミング言語で組んだ場合のURLが
    http://〜/○○○.php
    
    とかでこれがperlに変わると
    http://〜/○○○.pl
    
    とかになっちゃいかんということ。
    http://〜/○○○
    
    実装が変わったとしても上記で常にアクセスできるようにすべし。もっというと、以下のような仕様というか関数が見えるのもよくない。
    http://〜/login.php?action=show
    
    仕様が変わって関数名がshowから別のものに変わると永続性がなくなる。こういう場合は隠す。
    RewriteEngine On
    RewriteRule ^/login/?(.+)?$ login.php/$1?action=show [QSA]
    
    というかこの例ちょっとわかりにくい。それよりかは、
    RewriteEngine On
    RewriteRule ^/news/detail/([0-9]+)/? /news/detail.php?id=$1 [L]
    
    とかみたいな、最近の流行で言ってもらった方がわかりやすい。

簡易ラウンドロビン

RewriteMapディレクティブを使うと、ちょっとおもしろいことができる。 まず以下のように/var/www/server.lstを用意しておく。

img img1.example.com|img2.example.com

次にmod_rewriteの設定

RewriteEngine On
RewriteMap imgServers rnd:/var/www/server.lst
RewriteRule ^/img/(.+)$ http://${imgServers:img}/img/$1 [R=301]

こうすると、/img/配下のファイルにアクセスした場合、http://img1.example.jp/img/* と http://img2.example.jp/img/* にランダムにアクセスされ、簡易な負荷分散ができる。

  • .htaccessではできないので注意。

その他

  • ドキュメント読む、これ重要!
  • テストする、これ重要!
  • フレームワークなんか使ってる場合にREST対策で使うためってのが主流か


  1. 1. ただしブラウザのURLが書き変わる訳ではなく内部的に書き変わるだけ?