mod_rewriteのRewriteCondメモ

.htaccessでmod_rewriteを行った際に苦労したので備忘録としてまとめました。

目次

0.mod_rewriteとは?

HTTPサーバーに広く使用されているApache。その追加機能(モジュール)の1つがmod_rewriteです。
mod_rewriteを使用するとリクエストされたURLを読み替えたり、ある条件を満たした場合のみリダイレクトする、なんてことができます。

レンタルサーバー利用の際はサーバーがmod_rewriteをインストールしていなければ使用できないので、 mod_rewriteを利用できるか事前に確認しておくとよいでしょう。

通常、.htaccess、mod_rewriteと言えば、Apacheのものでしたが、Microsoft製のHTTPサーバーであるIISにも同様の機能(ただし、完全な互換性はない)があるようです。
ここでは、Apacheで使用する際に限定して内容をまとめています。

1.知っておきたい予備知識

ここでは、キーワードのみにとどめます。気になる場合は、検索! 検索!

2.mod_rewriteで何ができるの?

2-1.URLを『rewrite』するmod_rewrite

mod_rewriteでできることを1行でまとめるなら、

「1つのURLへのアクセスを条件ごとに複数のページに振り分ける、逆に複数のページのURLへのアクセスを1つのCGIに集約する」

ということになるでしょうか。

2-2.書式

.htaccessで以下のようなルールを記述します。
# mod_rewriteを有効にする
RewriteEngine On

# /result.htmlにアクセスがあった場合に/result_sc.htmlの内容を返すようにする
RewriteCond %{THE_REQUEST} ^.*\s/result\.html[\s\?].*$
RewriteRule ^(.*)$ result_sc.html [L]

# /result_ok.htmlにアクセスがあった場合に/result.htmlのへリダイレクトする(ブラウザのURLがresult.htmlに変わる)
RewriteCond %{THE_REQUEST} ^.*\s/result_ok\.html[\s\?].*$
RewriteRule ^(.*)$ result.html [R=301,L]

# 上の条件にマッチしなかった場合はこちらで処理される(RewriteCondがないので上の条件にマッチしなかったすべてのリクエストに対して行われる)
RewriteRule ^(.*)$ index.cgi [L,QSA]
上の例では、result_ok.htmlにアクセスすると、result.htmlへリダイレクトされた後、result_sc.htmlの内容を受け取ります。
(ブラウザのアドレスバーにはresult.htmlと表示される)

さらに、result_sc.htmlへアクセスすると、index.cgiの結果を受け取ります。
(ブラウザのアドレスバーにはresult_sc.htmlと表示される)

mod_rewriteは便利な半面、URLが置き換わることによって処理の流れが分かりづらくなるというデメリットもあります。

上の例はその典型で、リダイレクト先がrewriteされるため、直感として分かりづらいケースです。
mod_rewriteを使用する場合は置き換えのルールだけでなく、置き換えられたURLがどう処理されるのかを考える必要があります。

参考ページ

まとめページは参考になりますが、やはり本家の情報を確認しておくと理解が深まります。
本家のmod_rewrite詳細 (英文です)

3.RewriteCond条件いろいろ

3-1.IPで振り分け

# IPが123.45.67.89の場合処理する。
RewriteCond %{REMOTE_ADDR} =123.45.67.89

3-2.WEBブラウザ(UserAgent)で振り分ける

# user_agentがMozillaから始まるブラウザからのアクセスの場合処理する
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*$

3-3.リクエストで振り分ける

# /sample/index.htmlにアクセスがあったのみ処理する
RewriteCond %{THE_REQUEST} ^.*\s/sample/index\.html[\s\?].*$

# 上の例でクエリ文字つきのものを除外したい場合
RewriteCond %{THE_REQUEST} ^.*\s/sample/index\.html\s.*$


ちなみに別サイトで見かけた以下の例。書式としては簡単ですが場合によっては意図した動作にならない可能性があります。

# /sample/index.htmlのアクセスを処理する?
RewriteCond %{THE_REQUEST} ^.*/sample/index.html
これは以下の %{THE_REQUEST} の文字列に対し、マッチするのを期待していると思われます。
GET /sample/index.html HTTP/1.1
確かに/sample/index.htmlにアクセスした時にきちんと動作します。
ですが、正規表現を読み解くと実は以下のようなリクエストにもマッチしてしまいます。
GET /aaaa/bbbb/sample/indexzhtmlll?abc=de HTTP/1.1
こんなアドレスに誰がアクセスするんだというツッコミはさておき、
ブラウザのURLアドレスバーから直接入力すれば誰だってアクセスは可能なのです。

このように、
RewriteCond %{THE_REQUEST} ^.*/sample/index.html

というルールは/sample/index.htmlだけを処理するものではなく、/sample/index.htmlを含む数多くのURLにマッチするということは認識しておくべきです。
仮に/sample/aaa/sample/index.htmlというものがあれば、そのページにアクセスした場合も(ちゃんと)処理されます。

特に置き換えたURLをパラメータとしてCGIに渡している場合には気をつけたいところです。
不正なアクセスに対して対策がなされていなければ、セキュリティホールになってしまう可能性があるからです。

4.陥りがちなミス

4-1.演算子の間に空白文字を入れてはいけない

# NGの例( =との間に空白があるのでエラーとなる)
RewriteCond %{REMOTE_ADDR} = 59.84.148.191

4-2.URIエンコードをした結果で記述する(特に半角スペースは気づきにくい)

半角スペース他、URLとして不適な文字列が含まれるとエラーとなる
URLエンコードした形で表現し、半角スペースはの場合%20と記述します。

# NGの例(半角スペースが含まれるためエラーとなる)
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/5.0 (Windows NT 6.3).*$
# 半角スペースは%20と記述
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/5.0%20(Windows%20NT%206.3).*$

4-3.正規表現で使われる特殊文字でマッチさせたくない場合はしっかりとエスケープする

# 正規表現での . は任意の文字のマッチを意味するので、過判定を無くしたかったらエスケープする
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/5\.0%20(Windows%20NT%206\.3).*$