それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Apacheのmod_rewriteによるURL書換についてのまとめ(1)RewriteRule+RewriteCond

mod_rewrite は黒魔術ですが・・・

黒魔術でわかりにくい mod_rewrite 。どうしても理解できないmod_rewriteです。というか考えたくない。そのためにコピペで済ませてしまう。

基本的な処理の流れや記述方法を調べようと思ってもあまり資料が無かったりする。

面倒がらず、基本的な処理の流れや条件の書き方を調べ、まとめようと思い立った

mod_rewrite をちゃんと使いたい。

mod_rewrite の幾つかの機能を組み合わせると、出来過ぎるので、資料をまとめるのも一苦労。

まとめの方向性はは次の通りを考えています。

  1. rewrite を追いかける
  2. エラーログ。
  3. rewrite rule で書換
  4. rewrite rule を複数
  5. cond 引数と条件
  6. cond 組合せ
  7. 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/.htaccessmod_rewriteの設定を記述していきます。

get.php の中身

get.phpはリクエストを書き出すだけ。

Rewriteが発動し、リクエスト書換を確認できるよう、シンプルにGET引数だけを見ることにしました。

<?php var_dump($_GET);

mod_rewriteの設定は、すべてのこのget.phpへ向けて書換を行うよう記述する予定です。

サンプル .htaccessの記述例

.htaccessmod_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へリクエストを送信すると、Apachemod_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をペアで使っています。

  • RewriteCond で画像拡張子(gif|png|jpg|jpeg|bmp)に限定
  • RewriteRule でURLを書き換えてリダイレクト

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 の場合。

  1. rule1 にマッチ、
  2. rule2 にマッチ
  3. コレ以降にルールが無い、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 による処理をまとめておこうと思います。