mod_rewrite は黒魔術ですが・・・
黒魔術でわかりにくい mod_rewrite 。どうしても理解できないmod_rewriteです。というか考えたくない。そのためにコピペで済ませてしまう。
基本的な処理の流れや記述方法を調べようと思ってもあまり資料が無かったりする。
面倒がらず、基本的な処理の流れや条件の書き方を調べ、まとめようと思い立った
mod_rewrite をちゃんと使いたい。
mod_rewrite の幾つかの機能を組み合わせると、出来過ぎるので、資料をまとめるのも一苦労。
まとめの方向性はは次の通りを考えています。
- rewrite を追いかける
- エラーログ。
- rewrite rule で書換
- rewrite rule を複数
- cond 引数と条件
- cond 組合せ
- rewrite map でプログラムを使う。
このながれでまとめていきたい。
1. Rewriteの用語整理
まずはじめに、mod_rewrite で出てくる用語を整理しておきます、
- RewriteCond -> Cond = Condition -> Condition = 条件
- RewriteRule -> Rule -> 書き換え規則
- RewriteMap -> マッピング -> 対応表
mod_rewrieでは、RewriteRuleがメインで、RewriteCondが条件になる。
Rule(ルール)を適用する条件(Condition) になる。
また、RewrteMap には書き換え規則をハッシュマップ(連想配列、ディクショナリ)で記述し、RewriteCondやRewriteRuleの記述を簡潔にできる。
RewriteMapにつかえるフォーマット・タイプ。
RewriteMapの書換表には、フォーマットが幾つか用意されています。
- txt
- rnd
- dbm
- int
- prg
これらのフォーマットが使える。
さらに、参考資料によると用意済タイプ(txt/prg など) の他に独自タイプ(関数)を定義してタイプとして使えるそうだ。
2.1 Rewrite の記述の方法
基本的な記述方法について
ルールをシンプルに記述した基本的な記述方法になります。
はじめに憶える記述方法
.htaccess に記述する内容は、次のとおりです。
RewriteRule .* /index.php
mod_rewrite本体のロードは httpd.conf で行います、殆どの場合デフォルトでロードされています。
もし、mod_rewrite がロードされていてて、書換出来ない場合、このときは有効化がされてない可能性があります。
Rewriteの有効化と基本ルール
次のとおりに、Rewriteを有効化し、書換のルールを記述します。
RewriteEngine On RewriteRule ^.+ /index.php
RewriteRuleの記述内容は
RewriteRuleの記述形式を、日本語で表記すると、次の通りになります。
RewriteRule 正規表現 書換 オプション
RewriteRuleは、単なる正規表現での置換と考えて差し支えないとおもいます。
記述例をプログラム的に表現すると
URLがStringとしたら、 RewriteRuleは、String urlのメソッド呼び出しと解釈できます。
私は、String#replace または URL.replaceを呼び出していると解釈しています。
function RewriteRule(String Regex , String replacement , String Flags ) { location.replace( regex, replacement, flags||null ); }
私は、RewriteRuleは上記のJavaScriptに非常に近い処理をしていると考えています。。
2.2 書換を実際に体験してみる。
基本ルールが分かったので、次に書換を試してみようと思います。
次ようなディレクトリ構造を想定し、実際に書換を行いながら、RewriteRuleについて理解を深めていきましょう。
ディレクトリ構造を作りました
実験環境として、次のようなディレクトリ構造を作成しました。
~/public_html/rewrite-sample/ ├── ex1 │ ├── .htaccess │ └── index.php ├── get.php └── info.php 1 directory, 4 files
このex1/.htaccess に mod_rewriteの設定を記述していきます。
get.php の中身
get.phpはリクエストを書き出すだけ。
Rewriteが発動し、リクエスト書換を確認できるよう、シンプルにGET引数だけを見ることにしました。
<?php var_dump($_GET);
mod_rewriteの設定は、すべてのこのget.phpへ向けて書換を行うよう記述する予定です。
サンプル .htaccessの記述例
.htaccessにmod_rewrite サンプルを用意します。
このサンプルでは、.htaccess がある親ディレクトリの get.php に全て転送しています。
RewriteRule .* ../get.php?from=ex1
書換の結果がわかるようRewriteRuleがわかるように get パラメータに書換結果を保存して転送する予定です。
これで、準備は整いました。mod_rewrite によるリダイレクトを体験してみます。
実際にリクエストてみた結果
次は、リクエストをおこなってみた結果です。しっかり転送されてることがわかる。
存在しないファイルへのリクエストです。対象ディレクトリではmod_rewriteによる書換が有効になっています。
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hogehoge # リクエスト送信 array(1) { ["from"]=> string(3) "ex1" #書換結果 }
存在しないファイルへのリクエストですが、404(File not Found)になりません。 ポイントは、ブラウザから見えるURLはそのままで中身が変わってる点です。
curlコマンドは302リダイレクトを追いかけないので、結果が表示されるのは、URLはそのままで内部リダイレクトが起きたことを示しています。
この転送はApacheのリクエスト処理内部で行われブラウザには一切見えません。
全てのリクエストは、内部的に転送される。
サンプルが記述されたURLへリクエストを送信すると、Apacheとmod_rewriteはそれを書きまえました。
今回のサンプルでは、当該ディレクトリに対するリクエストは全て転送されます。
転送はApache内部で行われます、ブラウザに302は送信されません。
rewrite_rule の [R] オプションをつけると。
それでは、書換に依る内部リダイレクトの代わりに、Apacheから302を送信してみます。
素のRewriteRuleではブラウザには転送されたことが見えません。これをHTTPコードで見える形で書換と転送を行いわせます。
そのためには、Ruleに第三のオプションのフラグをつけてみる。
リダイレクト・オプションをつけたRewriteRule
302を送信するのは、カンタンです。[R]をオプションとして末尾に追記するだけです。
RewriteRule .* ../get.php?from=ex1 [R]
リクエストしてみた結果
[R]を追記したURLへリクエストを送信して、結果を観察してみます。
ヘッダ情報を表示してリクエストを送信します。
takuya@~/Sites/rewrites$ curl -I http://localhost/~takuya/rewrites/ex1/あああああ HTTP/1.1 302 Found Date: Sun, 15 Nov 2015 13:58:04 GMT Server: Apache/2.4.16 (Unix) Location: http://localhost/Users/takuya/Sites/rewrites/ex1/../get.php?from=ex1 Content-Type: text/html; charset=iso-8859-1
転送は内部的ではなくなりました。ブラウザとの相互作用に変化しています。
[R]を追記すればブラウザにリダイレクト応答を送信するように変化ことが確認できた。
[F] オプションをつける。
rewrite は、リダイレクトや転送や書換を行うだけじゃない。
オプションでアクセス制限をつけうることも出来ます。
403を送信しUnauthorizedでブラウザにコンテンツ送信を拒否してみます。
[R] の代わりに [F] をつけれけば、該当URLを表示を禁止出来るとわかります。
RewriteRule .* ../get.php?from=ex1 [F] # オプション R→F へ変更
[F]を試してみた。
この設定をつけたURLへリクエストを送ります。
takuya@~/Sites/rewrites$ curl -I http://localhost/~takuya/rewrites/ex1/hoge.php HTTP/1.1 403 Forbidden
このときには、URL正規表現でマッチだけで十分なので、書換文字列の指定は無駄ですね。
書換は不要なので、書換の部分を「 - 」(ハイフン:なにもしない)に交換しておきます。
アクセス禁止をするときの記述例
マッチしたURLを書き換えずに、[F] を適用し403 forbidden にしアクセス禁止を実現しています。
RewriteRule .* - [F]
2.3 RewriteRule ここまでのまとめ
RewriteRule 正規表現 書き換え後ルール オプション
リクエストURI(GET引数は含まない)ものが正規表現にマッチするか調べる。
マッチすれば、書換ルールにしたがって処理。
マッチすれば、オプションでHTTPレスポンスのステータスを制御する
3. RewriteCond
それは、Ruleについてはひとまず終わって。RewriteCondについて見てきます。
RewriteCondはその名前の通り、「条件」を追加するもので、追加した条件はAndで結合されていきます。
URL以外も条件に使うにはRewriteCond
条件を決めて、条件にマッチしたものをRuleに引き渡す。
条件には「環境変数」が主に使われる
Cond例、画像(gif/jpg/bmp) を禁止する
画像拡張子にマッチさせます。
環境変数 REQUEST_FILENAMEにファイル名(basename)が格納されています。
そこで、RewriteCondに条件「環境変数 REQUEST_FILENAMEに指定拡張子が含まれる」を追加しました。
RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ RewriteRule .* ../get.php?from=ex1
このように書くと、ファイル名が画像拡張子にマッチするときに、内部転送される。
拡張子にマッチして転送される。
実際にリクエストをおこなってみました。
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hogehoge.jpg array(1) { ["from"]=> string(3) "ex1" }
hogehoge.jpg は存在しませんが、ファイル名としてマッチするのでRewrite対象になっています。
Condにマッチしない時
逆にマッチしない時には、通常リクエストとして扱われる。
通常リクエストとして、処理され、ファイルがないので404になっている。
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hogehoge <title>404 Not Found</title>
拡張子がなくマッチしません。ですから、通常リクエストとして扱われました。Rewriteの対象外になっています。
通常リクエストでリクエストが処理されましたが、ファイルが非存在のために404 File not Found が返されました。
RewriteCondに拡張子条件を入れることで、画像拡張子 and 正規表現マッチ
したものが、リダイレクト対象になっています。
全てのアクセスに対し拡張子で絞りこみました。URLのマッチも行われています。
Cond and Ruleの整理
ここまでで、RewriteRuleとCondをペアで使っています。
Condを入れると、( cond 指定条件 AND rule のURL正規表現 ) と、And で判定
されます。
先例は、正規表現がディレクトリ内の全ファイル対象なので、RuleのURL条件が、わかりにくい気がします。
Condに指定した拡張子はURLでもあるので、混乱しそうです。
RewriteCond と「Rule:URLにhogeを持つ」と組み合わせる
RewriteRule側の条件を細かくしてみます。
Rule側でファイル名やパス名に関するマッチを追加して、And条件で判定されていることを明確にしたいと思います。
「hoge文字列にマッチ」とRule条件に変えてみる
全パターンを指定せずに、URLの特定文字列の時だけと、Rule判定を追加します。
RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ RewriteRule .*hoge.* ../get.php?from=ex1 #hoge にマッチするURLに限定
URLがhogeを含み、拡張子が画像のものへのリクエスト
Rule And Condで判定されるので、以下のようにリクエストURLを変更すると、マッチと非マッチが明確に分離されます。
実際にリクエストした結果が次の通りになります。
hoge.jpg
hoge.jpg や hogehoge.jpg はマッチする。
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hoge.jpg array(1) { ["from"]=> string(3) "ex1" }
aaaa.jpg
a.jpgや x.jpg はマッチしない
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/a.jpg <title>404 Not Found</title>
hoge.php
hoge.php はruleにはマッチするが、Condにはマッチしない。
Rule AND Cond 判定なのでマッチしないと判断される。
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hoge.php <title>404 Not Found</title>
RewriteCondを使うことで、条件をより複雑に出来る。もちろん諸刃の刃で、複雑になればなるほど解析と理解が苦しくなる。
まとめ RewriteCond
RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ RewriteRule .* ../get.php?from=ex1
RewriteCondは 直後のRewriteRule に反映される。
RuleがリクエストURLに正規表現マッチを試みるの対し、Condは環境変数に対して正規表現マッチを試みる
RewriteRule と RewriteCond はそれぞれANDで結合される。
4 複数Ruleを書く場合
このあたり方どんどん面倒になってくるのですが。頑張って記述してきます。
RewriteRule を複数書く場合。
RewriteRule .*hoge.* ../get.php?from=ex1-rule1 RewriteRule .* ../get.php?from=ex1-rule2
この条件を書いた時に、 rule1 / rule2 のどちらにマッチするのでしょうか?
hoge.jpg は rule1 に書いたとおりになるのでしょうか。
どちらが適用されるか?
実際に試してみるとわかります。
hoge.jpg へのアクセス
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hoge.jpg array(1) { ["from"]=> string(9) "ex1-rule2" }
a.jpg へのアクセス
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/a.jpg array(1) { ["from"]=> string(9) "ex1-rule2" }
Ruleは全て実行。
Ruleは全てチェックされます。そのためどちらも rule2 にマッチします。
a.jpg も hoge.jpgもどちらもrule2が適用された、コレには理由があります。
それは、RewriteRuleは順番に全てチェックするという「基本的処理の流れ」に従っているためのです。
hoge.jpg の場合。
- rule1 にマッチ、
- rule2 にマッチ
- コレ以降にルールが無い、rule2 を採用。
このように、Ruleは全て通して一番最後にマッチしたもので、それまでの結果を上書きということになります。
複数Ruleをプログラム的に表現
if ( rule1 ){ } if ( rule2 ){ }
とif 文が連打されているイメージになります。
マッチしたらソコで打ち止め
マッチしたらソコで打ち止めしたい。というのは多くの場合に便利だと思います。
通常の処理の流れ(上から順に、rule 全部にマッチさせる。)を取り払って、途中で処理の流れを外れる
RewriteRule .*hoge.* ../get.php?from=ex1-rule1 [L] RewriteRule .* ../get.php?from=ex1-rule2 [L]
[L] を追記でRule適用を中止
この[L] オプションを使うと、無事に、途中でRuleの連続から、脱出することが出来る。
takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/hoge.jpg array(1) { ["from"]=> string(9) "ex1-rule1" # 無事rule1で処理終了 } takuya@~/Sites/rewrites$ curl http://localhost/~takuya/rewrites/ex1/a.jpg array(1) { ["from"]=> string(9) "ex1-rule2" }
[L] オプションをプログラム的に表現
これは、プログラムでいうと、if文の中にreturn を書いた感じになる。
if ( rule1 ){ return } if ( rule2 ){ return }
if 文がreturn
と共に書いたと思えば理解の助けになるのではないかと。
CondとRuleを複数書く
Ruleの直前に書いて、Condを追加できる。
RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ RewriteRule .* ../get.php?from=ex1-rule1 [L] RewriteCond %{REQUEST_FILENAME} \.(php)$ RewriteRule .* ../get.php?from=ex1-rule2 [L]
[L] と cond の組合せですが、ここまでで、いくつか重要な事項が出てきます。
前半まとめ
- RewriteRule はURLに対して正規表現マッチ
- RewriteCond は環境変数に対して正規表現マッチ
- RewriteCond はRewriteRule の直前に書く
- RewriteRule は複数書くと全部マッチされる
- RewriteRule に [F] オプションを書くと 転送の代わりに 403 forbidden
- RewriteRule に [R] オプションを書くと 転送の代わりに HTTP 301 リダイレクト
- 複数RewriteRule に [L] を書くとそこでマッチを中止する
前半で出てきた Rewriteのサンプル
RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ RewriteRule .*hoge.* ../get.php?from=ex1-rule1 [L]
これをプログラム的に書くと
if( url.match(/.*hoge.*/) and REQUEST_FILENAME.match(/(gif|png|jpg|jpeg|bmp)/) ) { url.replace(/.*hoge.*/ , "../get.php?from=ex1-rule1" ); return }
このような「イメージ」になる。あくまで理解の助けのためのイメージですが、補助にはなると思います。
こうはんでは、RewriteCondの複数条件、RewriteMapと prg による処理をまとめておこうと思います。