はじめて利用する方でも分かるように、一から徹底的に解説します
リダイレクトやURLの書き換えを行うmod_rewrite。
呪文のように難しい記述に、何をやっているのか分からない挙動。
私自身、自動で生成されるものを利用したり、コピペで済ませていました。
しかし、とある案件でmod_rewriteについて学ぶ機会があったので、良い機会だと思い、mod_rewriteの使い方をまとめてみました。
目次
mod_rewriteの初期設定とログの表示方法
mod_rewriteの初期設定
ログファイルの設定
各ディレクティブの解説
RewriteBaseディレクティブ
RewriteCondディレクティブ
RewriteCondの後方参照について
テスト文字列に利用できる環境変数の一覧
条件パターンの一覧
オプションの一覧
RewriteEngineディレクティブ
RewriteMapディレクティブ
RewriteOptionsディレクティブ
RewriteRuleディレクティブ
RewriteRuleのフラグについて
実際の設定例とログから学ぶmod_rewriteの動作
Question2Answerで利用されているmod_rewriteの例
WordPressで使われているmod_rewriteの例
W3TC Page Cacheで使われているmod_rewriteの例
mod_rewriteでモバイルを振り分ける
サイト移転に伴う301リダイレクト
wwwありをwwwなしに301リダイレクト
http://example.com/index.htmlをhttp://example.com/へ301リダイレクト
mod_rewriteの初期設定とログの表示方法
mod_rewriteはApacheのモジュールです。mod_rewriteを利用することでURLを書き換えやリダイレクトを指定することができます。
例えば、PHPでパラメータを追加して動的なURLを作成している場合、URLは以下のようになります。
http://example.com/?p=123
mod_rewriteを利用すれば以下のような静的なURLでアクセスすることが可能になります。
http://example.com/123/
Apacheの内部では以下のように処理されています。
mod_rewriteの初期設定
mod_rewriteはApacheのモジュールなので設定ファイルで有効にする必要があります。サーバのroot権限を持っている場合は以下の手順で有効にしてください。
# vi /etc/httpd/conf/httpd.conf
xammpの場合は「C:\xampp\apache\conf\httpd.conf」を編集してください。
以下の行を追加してください。
LoadModule rewrite_module modules/mod_rewrite.so
レンタルサーバでは設定できませんが、現在では、ほとんどのレンタルサーバではデフォルトで有効になっています。
ログファイルの設定
mod_rewriteは正規表現や複数の条件を組み合わせて指定します。一見しただけでは挙動を把握しにくいため、専用のログを書き出す設定が用意されています。
先ほどと同じように設定ファイルに追記してください。
Linuxの場合
RewriteLog /var/log/httpd/rewrite.log RewriteLogLevel 9
XAMPPの場合
RewriteLog "logs/rewrite.log" RewriteLogLevel 9
設定したらApacheを再起動してください。
もしログが書き出されない場合はApacheの実行ユーザーの権限で指定した場所に空ファイルrewrite.logを作ってみてください。
mod_rewriteの検証が終わったらリソースの無駄になるので、コメントアウトするか削除して無効にしておいてください。ログを有効にすると顕著に重くなります。
各ディレクティブの解説
公式ドキュメントと同じ構成で解説しています。
初めてmod_rewriteについて学ぶ方は、まずは大まかに把握しておいてください。詳しい使い方は、後で例を交えて解説します。
RewriteBaseディレクティブ
Rewrite処理のベースになるURLを設定する。
httpd.confに記述する場合は特にいらないが、サブディレクトリに「.htaccsses」を設置する場合、相対パスの起点となるため重要になる。(絶対パスで指定している場合は不要)
書式
RewriteBase URL-path
例)ベースを/hogeにする
RewriteBase /hoge
RewriteCondディレクティブ
書き換えの条件を指定する。この条件に一致した場合だけ書き換えが行われる。
書式
RewriteCond テスト文字列 条件パターン オプション
例)ファイルが存在する場合、後に続くパターンで書き換えを行う
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCondの後方参照について
ここで後方参照について触れておきます。
RewriteCondの条件パターンで「()(カッコ)」を使って一致した値は、変数を使って再利用できます。これを後方参照と言います。
RewriteCondで指定したパターンを参照する場合は「%(パーセント)」を使います。「()(カッコ)」が複数ある場合は左から%1、%2といった具合に利用することができます。
同じように、後に解説するRewriteRuleディレクティブでも後方参照を利用できます。こちらは通常の正規表現の後方参照と同じように「$(ドル)」で利用できます。
例)http://example.com/ex/huga/index.htmlでアクセスした場合
RewriteCond %{REQUEST_FILENAME} ^(.*)/(.*)/index.html$ RewriteRule ^/ex/(.*)/(.*)$ /$1/%1/$2
以上のルールが実行されると、「/huga/ex/index.html」となります。%と$で、それぞれRewriteCondとRewriteRuleの後方参照を利用している点に注意してください。
テスト文字列に利用できる環境変数の一覧
mod_rewriteでは「HTTP環境変数」という特別な変数を利用できます。「HTTP環境変数」を利用するとURLだけでなく、Cookieの情報、ユーザーエージェント、ファイルやディレクトリの有無など、さまざまな条件を指定することができます。
利用できるものは以下のとおりです。
情報のタイプ | 環境変数 |
---|---|
HTTP headers: | HTTP_ACCEPT |
HTTP_COOKIE | |
HTTP_FORWARDED | |
HTTP_HOST | |
HTTP_PROXY_CONNECTION | |
HTTP_REFERER | |
HTTP_USER_AGENT | |
server internals: | DOCUMENT_ROOT |
SCRIPT_GROUP | |
SCRIPT_USER | |
SERVER_ADDR | |
SERVER_ADMIN | |
SERVER_NAME | |
SERVER_PORT | |
SERVER_PROTOCOL | |
SERVER_SOFTWARE | |
connection & request: | AUTH_TYPE |
CONN_REMOTE_ADDR | |
CONTEXT_PREFIX | |
CONTEXT_DOCUMENT_ROOT | |
IPV6 | |
PATH_INFO | |
QUERY_STRING | |
REMOTE_ADDR | |
REMOTE_HOST | |
REMOTE_IDENT | |
REMOTE_PORT | |
REMOTE_USER | |
REQUEST_METHOD | |
SCRIPT_FILENAME | |
date and time: | TIME_YEAR |
TIME_MON | |
TIME_DAY | |
TIME_HOUR | |
TIME_MIN | |
TIME_SEC | |
TIME_WDAY | |
TIME | |
specials: | API_VERSION |
CONN_REMOTE_ADDR | |
HTTPS | |
IS_SUBREQ | |
REMOTE_ADDR | |
REQUEST_FILENAME | |
REQUEST_SCHEME | |
REQUEST_URI | |
THE_REQUEST |
ちなみにLinuxやWindowsでも環境変数が利用できますが、Apache上で利用する「HTTP環境変数」とは別のものなので注意。「HTTP環境変数」はApacheの内部構造体に保持されています。
例えば「HTTP_HOST」の値を変えたい場合、Linux上の「/etc/hosts」を編集しても反映されません。変更には「/etc/httpd/conf/httpd.conf」のServerNameを変更する必要があります。
また、「HTTP_HOST」等はPHPやCGIで取得できますが、「HTTP_FORWARDED」など、Apacheの設定ファイルでしか取得できないものも多数あります。
条件パターンの一覧
RewriteCondでは条件に一致するパターンをPerl互換の正規表現で指定します。
例えば「!hoge」とあれば、先頭の「!」は否定なので、「hoge」一致しなかった場合となります。
以下のような演算子が利用できます。
パターン | 意味 |
---|---|
! | 否定 |
< | テスト文字列より上の場合true |
> | テスト文字列より下の場合true |
= | テスト文字列と等しい場合true |
<= | テスト文字列以上の場合true |
>= | テスト文字列以下の場合true |
-d | (is directory)ディレクトリが存在すればtrue |
-f | (is regular file)ファイルが存在すればtrue |
-eq | (is numerically equal to)いわゆる「=」 |
-ge | (is numerically greater than or equal to)いわゆる「<=」 |
-gt | (is numerically greater than)いわゆる「<」 |
-le | (is numerically less than or equal to)いわゆる「>=」 |
-lt | (is numerically less than)いわゆる「>」 |
-F | (is existing file, via subrequest)アクセス可能な有効なパスを指している場合にtrue (内部サブリクエストを用いてcheckされる) |
-l | (is symbolic link)シンボルリンク(-H、-Lも同じ意味。ちなみに小文字エルです) |
-s | (is regular file, with size)ファイルが存在し、サイズが0でなければtrue |
-U | (is existing URL, via subrequest)アクセス可能な有効なURLとなっている場合にtrue |
-x | (has executable permissions)実行権限がある場合にtrue |
オプションの一覧
オプション | 意味 |
---|---|
OR | 連続した複数の条件の内、どちらかに一致した場合に実行する。指定しない場合はANDとなり、連続した全ての条件が一致した場合にだけ実行する。 |
NC | 大文字小文字を区別しない。 |
RewriteEngineディレクティブ
RewriteEngineをオン・オフするだけのなんとも間の抜けたディレクティブ。
書式
RewriteEngine on|off
例
RewriteBase on
RewriteMapディレクティブ
リライトのパターンや処理を別ファイルで実行する。このディレクティブは1度定義して、利用します。
書式
RewriteMap マップネーム マップタイプ:マップソース ${ マップネーム : 後方参照のキー | デフォルトの値 }
例)「/path/to/file/map.txt」にexamplemapというMapNameを付ける
RewriteMap examplemap txt:/path/to/file/map.txt RewriteRule ^/ex/(.*) ${examplemap:$1|0}
ちなみにRewriteMapの指定は「.htaccsess」には書けません。httpd.confで指定する必要があります。
正規表現「.*」の後方参照「$1」に「examplemap」のルールを実行する。何も値がない場合は「0」となる。
httpd.confで指定していれば、こちらは「.htaccsess」に記述することが可能。
マップタイプで「txt」を指定した場合のマップファイルの書式は以下のとおりです。
元となる文字列 置き換える文字列
特定の文字列と対応する数字を置き換えたり、特定のディレクトリにアクセスしたらリダイレクトさせたりすることができます。
指定するMapSourceはPerl・CGI、PHPなどで動的なルールを指定することもできる。
例)url_rewriter.phpを指定する。
RewriteMap examplemap prg:/path/to/file/url_rewriter.php
url_rewriter.phpの中身
#!/usr/local/bin/php <?php $f = fopen( 'php://stdin', 'r' ); while( $line = fgets( $f ) ) { echo $line."hoge"; } fclose( $f ); ?>
この例では標準入力に対してhogeをくっつけて出力している。
記述ミス等があると応答を待ってApache自体が停止してしまうので注意してください。また単純な書き換えはtxt形式や後述するRewriteRuleディレクティブを利用したほうが高速です。
RewriteOptionsディレクティブ
リライトのオプションを指定する。サーバ単位の設定と、ディレクトリ単位の設定で利用できる。
書式
RewriteOptions オプション
オプション | 意味 |
---|---|
Inherit | 通常親ディレクトリと子ディレクトリにルールを追加すると子ディレクトリのルールが優先されて有効になる。inheritを指定すると親ディレクトリの指定が優先して実行されるようになる。 |
InheritBefore | こちらは子ディレクトリのルールを実行した後に、親ディレクトリのルールが実行される。 |
InheritDownBefore | 記述したディレクトリより下のディレクトリのルールを実行してから実行する。 |
IgnoreInherit | 親ディレクトリのルールを破棄して現在と子ディレクトリのルールを実行する。 |
AllowNoSlash | ディレクトリのマッチにスラッシュがなくてもマッチするようになる。 |
AllowAnyURI | どのようなURIでも書き換えることができるようになる。過去書き換えルールの脆弱性の発見で対策を行ったが、その問題の発見前に戻すことができる。セキュリティ上問題があるため、限られた条件の時以外は使わないこと。 |
RewriteRuleディレクティブ
書き換えのルールを決めるディレティブ。パターンで一致した値を置換文字列で書き換えます。
条件パターンはPerl互換の正規表現で指定する。
書式
RewriteRule 条件パターン 置換文字列 [フラグ]
RewriteRuleのフラグについて
RewriteRuleで利用できるフラグは挙動を制御するために重要な指定となります。しっかりと内容を理解して利用するようにしてください。
フラグ | 意味 |
---|---|
B | 英数字のエスケープ。いわゆるURLエンコード(&を%26など)。 |
chain|C | チェーン。次の処理を連続して行う場合に指定する。 |
cookie|CO=NAME:VAL | Cookieをセットすることができる。書式:CO=NAME:VAL:domain[:lifetime[:path[:secure[:httponly]]]] |
discardpath|DPI | PATH_INFOの削除。PATH_INFOとは実際のスクリプトファイル名とクエリ文字列の間にある、クライアントが提供するパス名情報。 |
END | リライト処理を停止する。Lはルールセットだけだが、ENDの場合は.htaccess単位で終了するので後のリライトは無効になる。v2.2では使えなくなったようです。 |
env|E=[!]VAR[:VAL] | 環境変数をセットできます。セットするときは[E=VAR:VAL]、削除するときは[E=!VAR]とする。例)hogeという閑居言う変数にhugaを入れる。[E=hoge:huga] |
forbidden|F | クライアントに403 Forbiddenステータスコードを返す。 |
gone|G | クライアントに410 Goneステータスコードを返す。 |
Handler|H=Content-handler | 実行するアプリケーションを指定するハンドラを強制的に付ける。application/x-httpd-phpなど。設定ファイルでAddType application/x-httpd-php .phpとしておけば同じハンドラの場合はPHPプログラムとして実行されてる。 |
last|L | Lフラグが付くとそこでルールセットが停止する。直後に続くルールを実行しない。 |
next|N | 書き換えを行ったら、ルールセットの最初からやり直す。 |
nocase|NC | no case。大文字小文字を区別しない。 |
noescape|NE | noescape。エスケープをしなくなる。通常は&や%はURLエスケープされるが、このフラグを使うとエスケープせずにそのままの文字になる。 |
nosubreq|NS | 現在のリクエストがサブリクエストの場合はルールをスキップ。 |
proxy|P | ルールに一致したリクエストをプロキシを介して処理する。以降の処理はプロキシに渡るのでLフラグと同じようにルールの進行は停止する。 |
passthrough|PT | uri フィールドに filename フィールドの値をセットする。直後にmod_aliasでディレクトリ構造の書き換えを行う際などに利用する。 |
qsappend|QSA | クエリ文字列部分に書き換えではなく、文字を追加したい場合に指定する。通常「?」後のクエリ文字列は削除されるため利用する。 |
qsdiscard|QSD | クエリ文字列がある場合削除される。これはデフォルトの動作。 |
redirect|R[=code] | クライアントに外部へのリダイレクトとレスポンスコードを返す。何も指定しないと302で返す。[L=301]等とすればレスポンスコードを変更できる。 |
skip|S=num | 指定した数のルールをスキップする。[s=2]とすれば、一致した場合、以降のルールを2つ飛ばす。if-then-elseを実現できる。 |
type|T=MIME-type | 一致した項目にMIME-typeを付加する。AddTypeと同じ挙動。 |
例)任意の一文字をhogeに変換する
RewriteRule . hoge
これでひと通りmod_rewriteの書式は解説しました。次は実例を通して設定方法を学んでいきます。
実際の設定例とログから学ぶmod_rewriteの動作
理解を深めるには実例を見るのが一番です。
よく使われるmod_rewriteの設定を細かく分解して学んでいきます。
Question2Answerで利用されているmod_rewriteの例
Q&Aを作成できるQuestion2Answerではトップページに「.htaccsess」が作成され、以下のように追記されます。
DirectoryIndex index.php <IfModule mod_rewrite.c> RewriteEngine On #RewriteBase / RewriteCond %{REQUEST_URI} ^(.*)//(.*)$ RewriteRule . %1/%2 [R=301,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^.*$ index.php?qa-rewrite=$0&%{QUERY_STRING} [L] </IfModule>
1行目「DirectoryIndex」はそのままindexファイルを決める設定です。
2行目ここからmod_rewrite用の設定。「mod_rewrite」モジュールを読み込む。
3行目「RewriteEngine On」mod_rewriteが有効になる。
4行目「RewriteBase」相対URLを指定した場合重要になる。ここではコメントアウトされている。カレントディレクトリのため、敢えて記述する意味もないということか?
5行目「RewriteCond」ここでルールを適用する条件を指定する。
この例ではサーバ変数「REQUEST_URI」を参照して、「^(.*)//(.*)$」という記述があれば下に記述したルールを実行する。
「REQUEST_URI」は「ブラウザから送られてきた要求のURI」を返す。「http://example.com/hoge.html」であれば、ドメインの部分を抜いた「/hoge.html」が帰ってくる。
「^(.*)//(.*)$」は、行頭(^)から、任意の一文字の繰り返し(.*)、スラッシュ2つ(//)、任意の一文字の繰り返し(.*)が行末($)となる。つまり「hoge//huga」という形式の場合、後に続くRewriteRuleを実行するという条件。
注意点としてRewriteCondの直後に来たRewriteRuleしか有効にならない。2行続けてRewriteRuleを記述しても下の行にはRewriteCondで一致した項目は渡されない。 もし同じRewriteCondを適応するには以下のように二回書くか、フラグでルールの継続を明示する必要がある。
RewriteCond %{REQUEST_URI} ^(.*)//(.*)$ RewriteRule . hoge RewriteCond %{REQUEST_URI} ^(.*)//(.*)$ RewriteRule . huga
もしくは[C]フラグを使って、チェーンする。
RewriteCond %{REQUEST_URI} ^(.*)//(.*)$ RewriteRule . hoge[C] RewriteRule . huga
6行目「RewriteRule」ここでとうとう書き換えのルール。
%1と%2はRewriteCondの後方参照。一般の正規表現でいう、$1や$2に当たる。つまり、「hoge//huga」の場合、「%1」には「hoge」が、「%2」には「huga]が入っている。
オプション[L]でRewrite機能によるURL変換を終了。(後のルールが適用されない)
オプション[R=301]はステータスコードを301に変更する。301は恒久的に転送するという意味。
まとめるとRewriteCondの正規表現で「^(.*)//(.*)$」に一致した場合、一致した項目を「%1/%2」の形に恒久的に置き換えて、そこで書き換えルールを停止する。という書き換えを行っている。
続いてログファイルを見て、意図したとおり書き換えが行われているのか調べてみます。
ログファイルの見方
今までの解説から分かる通り、mod_rewriteは正規表現やフラグの指定が複雑に作用しており、挙動を把握するのは容易ではありません。
ログを見ながら正しく書き換えが行われているかチェックしてください。
ログファイルは以下の様な形式で書きだされます。(xamppの場合)
::1 - - [02/Jun/2014:13:09:46 +0900] [localhost/sid#f235f0][rid#4fa018e8/initial] (3) [perdir C:/xampp/htdocs/xampp/sukegra/php/] strip per-dir prefix: C:/xampp/htdocs/xampp/sukegra/php/qa-theme/sukegra_php/js/heightLine.js -> qa-theme/sukegra_php/js/heightLine.js
「::1から[rid#4fa018e8/initial]」まではIP、日時、ホストなどの情報。
「(3)」はログレベル。1~4まである。
「[perdir C:/xampp/htdocs/xampp/sukegra/php/]」の「perdir」はmod_rewriteのコンテキスト。この値を元にRewriteRuleで置き換えを行う。
このページでは書き換えとは直接関係のない部分は削除して見ていきます。
strip per-dir prefix: C:/xampp/htdocs/xampp/sukegra/php/qa-theme/sukegra_php/js/heightLine.js -> qa-theme/sukegra_php/js/heightLine.js applying pattern '.' to uri 'qa-theme/sukegra_php/js/heightLine.js' RewriteCond: input='/xampp/sukegra/php/qa-theme/sukegra_php//js/heightLine.js' pattern='^(.*)//(.*)$' => matched rewrite 'qa-theme/sukegra_php/js/heightLine.js' -> '/xampp/sukegra/php/qa-theme/sukegra_php/js/heightLine.js' explicitly forcing redirect with http://localhost/xampp/sukegra/php/qa-theme/sukegra_php/js/heightLine.js escaping http://localhost/xampp/sukegra/php/qa-theme/sukegra_php/js/heightLine.js for redirect redirect to http://localhost/xampp/sukegra/php/qa-theme/sukegra_php/js/heightLine.js [REDIRECT/301]
1行目「strip per-dir prefix」はサーバ側のファイルのフルパスからperdirの値を抜いたファイルのパスとなる。これが実際にRewriteRuleに渡される値。この値をルールによって書き換える。
整形された後のパスの先頭がスラッシュがない「qa-theme/」となっている点も注意。一致する条件で「/qa-theme/」としても一致しないので注意。
2行目「applying pattern」は検索パターン。RewriteRuleの部分です。
「‘.’ to uri ‘qa-theme/sukegra_php/js/heightLine.js’」となり、意図的に変更したスラッシュが2つある「js//heightLine.js」というURIが書き換えられています。
3行目続いて「RewriteCond」でルールを適用する条件に一致するか調べます。
「input」がテスト文字列で指定した%{REQUEST_URI}で、「^(.*)//(.*)$」が条件パターンにそれぞれ対応しています。
「matched」となっているので条件に一致していることがわかります。
ちなみに一致しない場合は「not-matched」となります。
書き換えの順序について ここで順序について疑問に思った方もいるのではないでしょうか? mod_rewriteの書式的には「RewriteCond」を判定した場合に、「RewriteRule」で書き換えるという指定でした。しかし内部的には「RewriteRule」で書き換えた後に、「RewriteCond」で判定されています。 じつはこれが正常な手順で、「RewriteRule」と「RewriteCond」が逆になります。
「rewrite」でURIがサーバのパスに置き換えられます。
「explicitly forcing redirect with」で実際にリダイレクトされて完成したURLを表示。
「escaping ~ for redirect」は[L]オプション。Rewrite機能によるURL変換を終了。これ以降一致した項目にルールは適用されません。
「redirect to [REDIRECT/301]」は[R=301]オプション。恒久的に転送する。
以上の流れでリダイレクトをしています。
続いている別の条件とルールを解説
今度は以下の3行について見ていきます。
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^.*$ index.php?qa-rewrite=$0&%{QUERY_STRING} [L]
7行目、再び一致の条件
「REQUEST_FILENAME」はリクエストにマッチしたファイルまたはスクリプトの、 完全なローカルファイルシステムのパス。
たとえば「REQUEST_URI」であれば「/file/test.jpg」となるところ、「REQUEST_FILENAME」なら「/var/www/htdocs/file/test.jpg」となる。
「!-f」は「-f」がファイル、「!」が否定なので「ファイルが存在しない場合」となる。
8行目、再び条件
「!-d」は同じように、「ディレクトリが存在しない場合」。
つまり両方合わせて「フォルダでもディレクトリでもない場合」という条件。特定のファイルやディレクトリでなく、PHPやCGIに渡される動的なURLの場合という意味。
9行目書き換えルール。
任意の一文字の繰り返し(^.*$)いわゆる一致した場合は何でも、「index.php?qa-rewrite=$0&%{QUERY_STRING}」という形に整形する。
「$0」はRewriteRuleの後方参照なので、「^.*$」で一致した値が入る。
「%{QUERY_STRING}」はサーバ環境変数。平たくいえば「?」の後に続く動的URLの部分。
再び上記の%{REQUEST_FILENAME} !-fと%{REQUEST_FILENAME} !-dで条件が一致した場合のログ
strip per-dir prefix: C:/xampp/htdocs/xampp/sukegra/php/questions -> questions applying pattern '^.*$' to uri 'questions' RewriteCond: input='C:/xampp/htdocs/xampp/sukegra/php/questions' pattern='!-f' => matched RewriteCond: input='C:/xampp/htdocs/xampp/sukegra/php/questions' pattern='!-d' => matched rewrite 'questions' -> 'index.php?qa-rewrite=questions&start=20' split uri=index.php?qa-rewrite=questions&start=20 -> uri=index.php, args=qa-rewrite=questions&start=20
「http://localhost/xampp/sukegra/php/questions?start=20」へアクセスした例です。
「strip per-dir prefix」で「questions」が「applying pattern」に渡される。
「RewriteCond」でファイルでもディレクトリでもない条件に一致。
「RewriteRule」である「index.php?qa-rewrite=$0&%{QUERY_STRING}」の形に整形される。
「index.php?qa-rewrite=questions&start=20」。「questions」の部分が「$0」で、「start=20」の部分が「%{QUERY_STRING}」にそれぞれ対応している。
WordPressで使われているmod_rewriteの例
それでは多くの方が利用しているWordPressの例を見て行きましょう。
WordPressではパーマリンクをデフォルト以外に設定すると、「.htaccess」に以下のようなコードを追加します。
ローカルの環境に「/xampp/oxynotes/」というフォルダを作り、WordPressをインストールした例です。
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase /xampp/oxynotes/ RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /xampp/oxynotes/index.php [L] </IfModule> # END WordPress
基本的にQuestion2Answerの形と同じですね。
まずRewriteCondで条件を付けずに「index.php」となっている場合はそこでルールを停止。
その他、ファイル名でもディレクトリでもない(動的URLの)場合は「/xampp/oxynotes/index.php」という形にリダイレクトされる。
この指定は「http://localhost/xampp/oxynotes/sample-post/」というURLを「http://localhost/xampp/oxynotes/index.php」という形に置き換えます。
上記のルールに一致しているパターンのログ
strip per-dir prefix: C:/xampp/htdocs/xampp/oxynotes/pukiwikiでもfacebook用にogpを設定する方法/ -> pukiwikiでもfacebook用にogpを設定する方法/ applying pattern '.' to uri 'pukiwikiでもfacebook用にogpを設定する方法/' RewriteCond: input='C:/xampp/htdocs/xampp/oxynotes/pukiwikiでもfacebook用にogpを設定する方法' pattern='!-f' => matched RewriteCond: input='C:/xampp/htdocs/xampp/oxynotes/pukiwikiでもfacebook用にogpを設定する方法' pattern='!-d' => matched rewrite 'pukiwikiでもfacebook用にogpを設定する方法/' -> '/xampp/oxynotes/index.php' trying to replace prefix C:/xampp/htdocs/xampp/oxynotes/ with /xampp/oxynotes/ internal redirect with /xampp/oxynotes/index.php [INTERNAL REDIRECT]
投稿名ベースのパーマリンクを指定し、「pukiwikiでもfacebook用にogpを設定する方法」というページにアクセスした場合の例。
「C:/xampp/htdocs/xampp/oxynotes/pukiwikiでもfacebook用にogpを設定する方法」というファイルやディレクトリが無いので条件に一致している。
そこで「pukiwikiでもfacebook用にogpを設定する方法/」を「xampp/oxynotes/index.php」に書き換えている。
「trying to replace prefix」の「/xampp/oxynotes/」の部分はRewriteBaseの部分。再度Apacheが内部的に問い合わせる際の接頭辞を指定する。
最後に「internal redirect」で内部的な問い合わせを「/xampp/oxynotes/index.php」で行うという設定になっている。
W3TC Page Cacheで使われているmod_rewriteの例
せっかくなので複雑な例も見ていきます。
WordPressでのキャッシュ系プラグインW3TC Page Cacheでページキャッシュを有効にすると作成されるコードを見ていきます。
W3TC Page Cacheの設定でPage cache method:をpage_enhancedにした場合。
# BEGIN W3TC Page Cache core <IfModule mod_rewrite.c> RewriteEngine On RewriteBase /xampp/oxynotes/ RewriteRule ^(.*\/)?w3tc_rewrite_test/?$ $1?w3tc_rewrite_test=1 [L] RewriteCond %{HTTPS} =on RewriteRule .* - [E=W3TC_SSL:_ssl] RewriteCond %{SERVER_PORT} =443 RewriteRule .* - [E=W3TC_SSL:_ssl] RewriteCond %{HTTP_COOKIE} w3tc_preview [NC] RewriteRule .* - [E=W3TC_PREVIEW:_preview] RewriteCond %{REQUEST_METHOD} !=POST RewriteCond %{QUERY_STRING} ="" RewriteCond %{REQUEST_URI} \/$ RewriteCond %{HTTP_COOKIE} !(comment_author|wp\-postpass|w3tc_logged_out|wordpress_logged_in) [NC] RewriteCond "%{DOCUMENT_ROOT}/xampp/oxynotes/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_SSL}%{ENV:W3TC_PREVIEW}.html" -f RewriteRule .* "/xampp/oxynotes/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_SSL}%{ENV:W3TC_PREVIEW}.html" [L] </IfModule> # END W3TC Page Cache core
5行目、いきなり「^(.*\/)?w3tc_rewrite_test/?$」を「$1?w3tc_rewrite_test=1」へ置き換える指定です。デバッグモードの時使うのかとおもいきや、どの条件でw3tc_rewrite_testとなるのか検証では分かりませんでした。(設定変更時のデバック用?)
6行目「%{HTTPS} =on」と、「%{SERVER_PORT}」は共にSSL通信を条件にしている。一致した場合は共に「[E=W3TC_SSL:_ssl]」となる。
Eフラグはenvironmentの略で独自の環境変数をセットする際に利用します。具体的には「W3TC_SSL」という環境変数に「_ssl」という値をセットしています。
10行目「%{HTTP_COOKIE}」でCookieの値が「w3tc_preview」に一致することを条件にしている。[NC]フラグがついてるので、大文字小文字を区別しない。
11行目SSLの時と同じように「W3TC_PREVIEW」という環境変数に「_preview」という値をセット。
プレビューモードをONにしてログインユーザーでアクセスした時のログ
ここでEフラグで追加した値がどのようにセットされているか見てみます。
Cookieに「w3tc_preview」という情報が付加されているのがわかる。
RewriteCond: input='wp-settings-1=editor%3Dhtml%26m4%3Do%26hidetb%3D1%26m6%3Dc%26m9%3Do%26imgsize%3Dfull%26widgets_access%3Don%26m5%3Do%26m3%3Do%26urlbutton%3Dnone%26align%3Dleft%26libraryContent%3Dbrowse; wp-settings-time-1=1399876848; wordpress_logged_in_237630aa553418786e07adbd3ce71036=admin%7C1402446969%7C772de8dbf4074d596134da04747420e4; qa_key=mtsbjs99vnlv0b2iwhm4663gcaah5bib; qa_session=oxy%2Fpilmllci%2F1; qa_noticed=1; w3tc_preview=1; __atuvc=8%7C20%2C0%7C21%2C5%7C22%2C36%7C23' pattern='w3tc_preview' [NC] => matched setting env variable 'W3TC_PREVIEW' to '_preview'
12~16行目まで複数の条件が並ぶ。
「%{REQUEST_METHOD}」リクエストのメソッド名(’GET’, ‘HEAD’, ‘POST’, ‘PUT’ など)が「POST」ではない(!)場合。ちなみに通常のアクセスはGET。投稿やコメントを送信する場合などにPOSTとなる。
「%{QUERY_STRING}」クエリ文字(URLパラメタ〈動的に作成される?bbb=cccなど〉)が「“”」空の場合。つまり、検索結果のページ(?s=検索)などはキャッシュされない。
「%{REQUEST_URI}」リクエストURLが「\/$」スラッシュで終わっている。タグベースや投稿名ベースのパーマリンクの場合は最後に「/」が付く。
では、数字ベースのパーマリンクの時はスラッシュが付かないが、どうするのだろうかとテストしてみると、数字ベースの時は、この条件が削除されていました。
「%{HTTP_COOKIE}」再びCookieで複数の条件を指定しています。「comment_author(コメント投稿者)」「wp\-postpass(パスワードで保護された投稿を閲覧している状態)」「w3tc_logged_out(w3tcがログアウト状態)」「wordpress_logged_in(WordPressにログインしている状態)」などがない(!)場合という条件。
[NC]フラグが付いているので大文字小文字を区別しない。
更に「/xampp/oxynotes/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_SSL}%{ENV:W3TC_PREVIEW}.html」というファイルが存在する(-f)場合。
上記の複雑な条件をクリアした場合に適用されるルールが「.* "/xampp/oxynotes/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_SSL}%{ENV:W3TC_PREVIEW}.html" [L]」となる。
具体的には「/xampp/oxynotes/wp-content/cache/page_enhanced/ホスト名/要求されたURL/セットしたW3TC_SSLの値(_ssl)/_indexセットしたW3TC_PREVIEWの値(_preview).html」というアドレスに書き換えれている。
つまりpage_enhancedでページキャッシュを有効にするとキャッシュ名は「_index.html」となり、SSL通信時の場合は「_index_ssl.html」となり、プレビューモードの場合は「_index_preview.html」となることがわかります。
mod_rewriteでモバイルを振り分ける
海外のサイトですがDetect Mobile Browsersでユーザーエージェントを元にモバイルを振り分けるためのコードが提供されています。
メールアドレスに一致させる正規表現と同じように、厳密に運用しようとするとこれだけ複雑になります。
サイト自体がテストとなっているので、試しにモバイルでアクセスしてみてください。
モバイルを振り分けるmod_rewriteのサンプル
RewriteEngine On RewriteBase / RewriteCond %{HTTP_USER_AGENT} (android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge\ |maemo|midp|mmp|mobile.+firefox|netfront|opera\ m(ob|in)i|palm(\ os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows\ (ce|phone)|xda|xiino [NC,OR] RewriteCond %{HTTP_USER_AGENT} ^(1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a\ wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r\ |s\ )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1\ u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp(\ i|ip)|hs\-c|ht(c(\-|\ |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac(\ |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt(\ |\/)|klon|kpt\ |kwc\-|kyo(c|k)|le(no|xi)|lg(\ g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-|\ |o|v)|zz)|mt(50|p1|v\ )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v\ )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-|\ )|webc|whit|wi(g\ |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-) [NC] RewriteRule ^$ http://detectmobilebrowser.com/mobile [R,L]
2つの条件(RewriteCond)に一致した場合に、http://detectmobilebrowser.com/mobileへ書き換えています。
一応ドコモ、ソフトバンク、auともにスマホやガラケーでアクセスしましたが正しくモバイルと認識していました。
簡単に日本国内のモバイルを判定したい場合は以下のようにすることもできます。
モバイルの場合は「/m」ディレクトリへリダイレクトする設定。
RewriteCond %{HTTP_USER_AGENT} ^(DoCoMo|J-PHONE|Vodafone|SoftBank|KDDI|PDXGW|WILLCOM|DDIPOKET|UP\.Browser) RewriteRule ^(.*)$ /m/ [L]
サイト移転に伴う301リダイレクト
サイトのドメインが移転によって変更になった場合は301リダイレクトをすることでスムースに移転させることができます。(ページランク等もある程度は引き継がれる)
hoge.example.comをhuga.example.comへ恒久的にリダイレクト
RewriteEngine On RewriteCond %{http_host} ^hoge.example.com RewriteRule ^(.*) http://huga.example.com/$1 [R=301,L]
wwwありをwwwなしに301リダイレクト
SEOの基本としてwwwありか、無しで統一するという技術があります。定番なので既に導入されている方も多いと思います。
www.example.comをexample.comへ301リダイレクト
RewriteEngine on RewriteCond %{HTTP_HOST} www\.example\.com RewriteRule ^(.*)$ http://example.com/$1 [R=301,L]
http://example.com/index.htmlをhttp://example.com/へ301リダイレクト
index.htmlを表示しない場合に使います。
RewriteRule ^index.html$ http://example.com/ [R=301,L]
動的URLを静的URLでアクセスできるように変更
WordPressで特にパーマリンクを指定しないと「?p=0」というパーマリンクになります。
それを静的URLでアクセスできるように変更します。
RewriteRule ^(.*)/$ ?p=$0 [L]
以上でmod_rewriteのまとめは終了です。
学べば学ぶほど奥が深いモジュールですが、正しく運用できれば、どんなURLも動的に作成することができます。普段はあまり意識することはありませんが、サイト設計から携わる場合はぜひ理解しておきたい技術です。
参考ページ:
http://httpd.apache.org/docs/2.4/ja/mod/mod_rewrite.html(公式)
http://japache.infoscience.co.jp/rewriteguide/ (公式の翻訳)
http://japache.infoscience.co.jp/japanese_1_3_6/manual/mod/mod_rewrite.html(バージョン1.3のmod_rewriteページの翻訳)
http://rfs.jp/server/apache/02apache/url_rewrite.html
http://ysklog.net/server/mod_rewrite/1390.html
http://tm.root-n.com/server:apache:module:rewrite
ENDフラグがap2.2で使えないもよう
情報ありがとうございます。修正しました。
こんにちは
-f フラグが2つ出てきます。。後の方は、-F でしょうか
>例)ファイルが存在する場合、後に続くパターンで書き換えを行う
>RewriteCond %{REQUEST_FILENAME} !-f
とあるところ、
ファイルが存在しなければ、の間違いではないかと思うのですが?
仰るとおりフラグの項目は、後に出てくる方は大文字で「-F」です。
解説の「RewriteCond %{REQUEST_FILENAME} !-f」は「!」なので「否定の存在しなければ」です。
雑な記事で混乱させてしまったようでしたら失礼しました。
頂いた情報を元に修正しました。ありがとうございました。
ピンバック: 近況です | via.Story
RewriteOptionsディレクティブの表中、InheritDownBeforeが二つ並んでいますが、二番目のInheritDownBeforeは、IgnoreInheritの間違いだと思います。
ご指摘ありがとうございます。
修正しました。