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

.htaccessに書く場合の注意

このように便利な Rewrite 機能は、個別ディレクトリに設置された .htaccess にも指定することができる。しかし、.htaccess に書けるのは RewriteEngine, RewriteBase, RewriteCond, RewriteRule だけで、ロギングに関する指定子などは書けないし、色々と注意すべきハマリ点がある。ここではそれらを一気に解説しようと思う。これは管理者になれない(=httpd.confを編集できない)一般ユーザにとっては重要な情報になるだろうね。

.htaccess は個別の公開ディレクトリに設置されて、細かい制御を出来るもので、よく個別ディレクトリにアクセス制限を入れたりするのに使う。しかし、いくつかの Apache 指定子は、ここにも書けるし、幸いなことに Rewrite機能(正確には mod_rewrite.so)は、この .htaccess に書くことも想定している。だがハマリ点がいろいろとあるのだ。このページはそういう .htaccess の特殊体質について解説しよう。

ちなみに、.htaccess は個別のリクエストごとに httpd によってその都度読み込まれ、言い替えればリクエスト毎に .htaccess がパースされて Rewrite ルールが適用されて、求めるリクエストに変換される。だから、.htaccess を書き換えても httpd の再起動は不要で、一般ユーザとしてはありがたいが、逆に言うと「大変効率は悪い...」のである。要するに .htaccess のパースはそれなりに高価であるし、Rewrite ルール自体の処理も高価なのである。もしあなたが管理者になれるのならば、.htaccess に書くのは避けた方が良いことを付言しておく。

  1. .htaccess に書ける条件〜AllowOverrideはどうなってる?
  2. 相対パス?絶対パス?
  3. サブディレクトリ問題




.htaccess に書ける条件〜AllowOverrideはどうなってる?

まず大前提である。.htaccess に書けば Rewrite機能が使える、というものではなく、「色々な指定子が .htaccess に書ける条件」が Apache にはある。これを制御するのが AllowOverride 指定子である。まあ、これはサーバのセキュリティ設定でもあるので、ガチガチのセキュリティを設定するケースでは、/etc/httpd/conf/httpd.conf(などの親設定ファイル)に、「AllowOverride None」と .htaccess の記述を無効にしているケースもあるだろう。このケースでは「そもそも .htaccess 自体が無効だから、Rewrite ルールなんて書きようがない...」のであり、もしあなたが管理者でなければ諦めるか、管理者に交渉する必要がある。

AllowOverride 自体は、適当な親ディレクトリ設定(言い替えれば httpd.conf の中の <Direcotry>節)に書き、それ以下のディレクトリの .htaccess で変更可能な内容を指定できる。つまり、祖先を含む親ディレクトリの設定に AllowOverride があって、この指定内容によって「.htaccess が何が出来るか?」が決まるのである。

では、具体的に AllowOverride に指定できる属性を解説しよう。

AuthConfig と AuthUserFile
これはパスワードによるアクセス制限を出来るようにする。まあ、これはアクセス制限を解説する文書じゃないので、説明はこれだけ。他の文献を当たってくれ。
Limit
これもアクセス制限だが、アクセス元IPなどで制限をするケースで指定する。これも他の文献を当たってくれ。
Indexes
これは http://hoge.or.jp/dirs/ みたいなURLでアクセスした時に、ディレクトリにあるファイルのリストを表示することがあるが、これを制御するもの。これも関係ない。
FileInfo と Options
さて、この2つの属性がお目当てである。まず、FileInfo は「AddType, AddEncoding, AddLanguage の上書きを許可する」と「Apache ハンドブック」には書かれているが、実質上これが Rewrite 機能を可能にするのに必要である。また、Options は Rewrite ルール適用場面で、「Options FollowSymLinks」か「Options SymLinksIfOwnerMatched」が指定されている(要するに書き換えがシンボリックリンク参照のような扱いを受ける)必要があるので、これが指定できるようにするのに必要である。

また、すべての上書きを可能にする「All」とすべてを不可にする「None」もあるので、結論として httpd.conf の中で、

<Directory "/var/www/html">
   AllowOverride FileInfo Options
#  か AllowOverride All
</Diredtory>

になっていないとダメなのである。OK?

相対パス?絶対パス?

さて、目出度く変換のしたいディレクトリに .htaccess 内に指定を書けるようになったわけである。httpd.conf と同様に、まず RewriteRule を有効にしてやるのが第1段階である。この解説ではそのディレクトリの実パスが(URLじゃなくて) /var/www/html/test で、言い替えれば DOCUMENT_ROOT が /var/www/html で、URLとしては http://www.hogehoge.or.jp/test のかたちでアクセスすることを想定しよう。/var/www/html/test/.htaccess は次のようになる。

Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test

RewriteBase は指定しなくても良いが、指定しておこう。Includes は実験のためSSIを有効にしたいだけである。どうせ「AllowOverride Options」が指定されているので、SSIはこれを指定すれば使える。

じゃ、具体的な書き換えを指定しよう。同じディレクトリにある「index.htm」を「jindex.shtml」に変換してみるのである。

Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test
RewriteRule index\.htm jindex.shtml

あれ!変換しないぞ....(404 File not Found が返る)一見正しく見えるんだが、実はこれはマチガイだ。正しく動かすには、こうしなければならない。

Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test
RewriteRule index\.htm /test/jindex.shtml

つまり、こうなのである。

RewriteRule 照合パターン 変換後の絶対URL

「変換後の絶対URL」は http://〜 から書いても良い(外部リダイレクトはそうなるね)し、サボって /〜 からでも良いが、ディレクトリ内部の相対URLではないのである。「照合パターン」がなんとなく相対URL風なので、「変換後も相対URLじゃないの?」と思いがちだが、これは違うのである! あくまで「照合パターン」なのである。ちょっと実験してみよう。

Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test
RewriteRule (.*)index\.htm /test/jindex.shtml?path=$1

この状態で jindex.shtml でSSIの機能を使って、レスポンスのボディでクエリ文字列を表示できるようにしておく。で、「/test/index.htm」とか「/test/test/index.htm」とかいうURLでアクセスしてみるのである。そうすると、これらは単にそのディレクトリを外した「index.htm」とか「test/index.htm」として照合されているのが判る。

結論としては「照合パターン」は「相対URL」のかたちで渡ってくるのだが、「変換後のURL」は「絶対URL」で書かなければならない!ということである。どうも直感に反する仕様だが、まあ、仕方がない。

サブディレクトリ問題

これは哲学的な問題である。ディレクトリはその中にさらにディレクトリが作れるからこそディレクトリなのだが、このようなディレクトリ入れ子構造と .htaccess はビミョーな問題が生じるのである。勿論、トップレベルの httpd.conf で RewriteRule を書くケースでは生じないのだが、RewriteRule はトップレベルでも、VirtualHost節でも、httpd.conf の中の Directory 節でも、.htaccess でも書けて、しかも照合パターンにはディレクトリを含めることが出来てしまう。更に、変換したパターンを更に別ディレクトリの指定で再変換することもできるわけで、考え出すと夜眠れなくなってしまう...

だから、この問題についてルールがある。Apache は次のような原理でこの問題に対応している。

  1. とにかく、照合パターンから引き出されるディレクトリ構成の中で、一番深い指定について、まず最初に RewriteRule を適用される。その後、変換後のディレクトリ指定に応じて、そのディレクトリに関する .htaccess の変換が適用される。つまり、「深い」指定が適用される。
  2. そして変換後のディレクトリについて処理を続ける。

おっと。それなりに一貫はしているんだが、なかなか戸惑うような結果が生じる場合もある。

次のサンプルで考えよう。/test のサブディレクトリ /test/sub と、両方に .htaccess がある状態である。

% cat test/.htaccess
Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test
RewriteRule sub/index\.htm /test/jindex.shtml?/test [L]

% cat test/sub/.htaccess
Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test/sub
RewriteRule index\.htm /test/jindex.shtml?/test/sub [L]

この時、RewriteRule は /test/sub/index.htm についてバッティングしている。URLでは両方とも同じ /test/sub/index.htm を参照し、それらが書き換えられるURLが異なっているのである。この時、ルールにより、一致の深い方のルールである /test/sub/.htaccess のルールだけが使われ、/test/.htaccess のルールは無視される。

まあ、この時には [L] で変換打ち切りを指定してある。これが影響しているのだろうか? いやいや、そんなことはない。[L] があっても、/test/sub/.htaccess で変換された後、/test/.htaccess で更に再変換することが可能である。

「RewriteRule はディレクトリ単位で確定する」のが筋であり、RewriteRule が尽きれば、その時点で変換は終って、具体的な変換後のファイルパス名に従って、処理を続けるのである。だから、変換後のファイルパス名が、そのディレクトリ(以下)に属するものであれば、その時点で変換は確定して、具体的なファイルの有無に応じたレスポンスが生成される。だから、上位ディレクトリの RewriteRule は必然的に無視されるのである。

逆に変換後のファイルパス名が上位ディレクトリに属するものになったケースでは、さらに上位ディレクトリでの .htaccess に書かれた変換ルールが適用される。つまり、一種の変換のチェーンが実行されてしまうのである。

# 親ディレクトリ側
% cat test/.htaccess
Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test
RewriteRule sub/index\.htm /test/jindex.shtml?/test [L]
RewriteRule tmp.htm /test/jindex.shtml?/test/sub/index.htm [L]

# サブディレクトリ側
% cat test/sub/.htaccess
Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test/sub
# 親ディレクトリのパス名に変換する
RewriteRule index\.htm /test/tmp.htm [L]

このケースではURLは次のように書き換えられて行く。

  1. /test/sub/index.htm(オリジナルのリクエスト)
  2. /test/tmp.htm(/test/sub/.htaccess により。実在しない)
  3. /test/jindex.shmtl?/test/sub/index.htm(/test/.htaccess により。完了)

「なるほど...それなりに使えそうだ...」と思うと甘い。ここからが落し穴だ。一般に .htaccess は、Rewrite ルール以外の用途でも使われる(というか、そっちのが多い)。セキュリティ設定をするなどの用途でこれを書いている方も多かろうし、特定ディレクトリに対して PHP などを使いたいケースで、設定していることも多かろう。不用意に RewriteRule を書いてしまうと、意外な副作用にお目にかかることになる。

# 親ディレクトリ側
% cat test/.htaccess
Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test
# 次行のルールが適用されると思うけど...
RewriteRule sub/index\.htm /test/jindex.shtml?/test [L]

# サブディレクトリ側
% cat test/sub/.htaccess
Options FollowSymLinks Includes
RewriteEngine on
RewriteBase /var/www/html/test/sub
# ルールなし。

では、/test/sub/index.htm は書き換えがなされない。これはサブディレクトリの .htaccess に書かれた変換ルールの結果(ルールがないのでそのまま評価)、無変換の具体的なファイルパス名として評価されてしまうわけだ。チェーンが実現できることから、何となく「親ディレクトリが変換残りを再変換してくれる」ように感じがちだが、これは違うのである。字面の変換が行われる結果、サブディレクトリを含む変換がなされるに過ぎないのであって、本質的にはディレクトリ構造を扱っているわけではないのである。

ということは、複数の .htaccess を使うケースでは次のことに気をつけなくてはならない。

  1. .htaccess に RewriteRule を書くケースでは、そのサブディレクトリ以下のファイルに適用されるべきルールは、すべての必要な変換を完全に書かなくてはならない。
  2. 結果として、親ディレクトリで書かれた RewriteRule をそのままサブディレクトリでもダブって書くことが多くなるだろう。
  3. 結果の扱いは「そのディレクトリ内部で具体的ファイル名として確定する」か、「上位ディレクトリのパス名に変換して、上位ディレクトリに処理を渡す」のどちらかである。
  4. 不必要な場所で、RewriteEngine を ON にしてはいけない。




copyright by K.Sugiura, 1996-2006