.htaccess
でRewriteRule
書くときに、安易に[L]
フラグを使うと思ったように動かないかもしれない。
仕組みを理解して使えば単純な事なんですがね!
.htaccess でアクセスを振り分けたい!
Apache HTTP サーバーを使っていると、割とよく使う .htaccess
ファイル。
特にアクセスをうまく振り分ける為に、 mod_rewrite
を使用してる方も結構いらっしゃいますよね。
なんて言ったって、天下の Wordpress 様が使っておられますから。
Wordpress では、アクセスを基本的に index.php
に集めて、そこから URL 解析をして特定のページに振り分けてますよね。
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
それぞれの文法については、既に多くの解説記事が Web 上に存在するのでそちらを見て貰うとして、
実はこの機能、勘違いしやすい部分があるよ、と言うお話です。
[L]フラグが効いていない?
ちょっとした問題ですが、.htaccess
ファイルに次の記述があるとき、
index1.html
にアクセスすると、最終的に開かれるファイルは何になるでしょうか。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule index1\.html /index2.html [L]
RewriteRule index2\.html /index3.html [L]
</IfModule>
解説のサイトを参照すると、大抵の所では
「[L]
が後ろについている場合にはそこで振り分けを終了する。」
と説明されていますね。
ということは、順当に考えれば
「index.html
へのアクセスはindex2.html
へのアクセスへと書き換えられる。
そこで振り分けを終了するので次の行のルールは実行されず、index2.html
が開かれる。」
ということになります。
では実際に実験してみましょう。
「アイエエエ!? index3.html
!? index3.html
ナンデ!?」
「なぜか [L]
を無視して2つ目のルールも適用されて、index3.html
にアクセスされている?」
…と言うことが起こるのです。
何が起こっているのか?
実はこれ、確かに[L]
のフラグは効いているのです。
ですから一旦、RewriteRule
での振り分けが終了し、index2.html
へアクセスしようとします。
しかし、公式ドキュメントを参照してみると…?
The simplified form of this is that once the rules have been processed, the rewritten request is handed back to the URL parsing engine to do what it may with it.
RewriteRule Flags - Apache HTTP Server Version 2.4
「この処理は一度書き換えを行った後、そのアクセスを行えるかどうかを判別する為にURLパースエンジンに書き換えたものを戻すよ」
と書かれています。
と言う事は、書き換えが行われた場合は処理を戻すということなので、
.htaccess
のmod_rewrite
についても、もう一度処理されるということですね!
今回の場合は、
{index1.html
をindex2.html
に変換して、[L]フラグにより変換を一度終了}
↓
{URLが変更されたので処理が戻る}
↓
{二週目はindex2.html
になっているので、一つ目のルールを飛ばして二つ目に適合してindex3.html
に変換される}
↓
{URLが変更されたので処理が戻る}
↓
{三週目はindex3.html
になっているので、どちらのルールにも適合しない}
↓
{URLに変更がなかったので処理が進む}
と言うように何度も処理が繰り返されていたのですね。
じゃあ、 .htaccess ファイルで [L] フラグは無意味ってこと?
結局もう一度処理されてしまうのであれば、[L]
のフラグは無意味だと言うことになるのでしょうか?
いえ、そういうことではないようです。
もう一度 Wordpress の.htaccess
ファイルを見てみましょう。
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
4行目のRewriteRule ^index\.php$ - [L]
には[L]
がついていますね。
ここで、7行目のRewriteRule . /index.php [L]
が適用されたパターンを考えてみましょう。
7行目では、ここまで到達したアドレスを全て/index.php
へ書き換えています。
で、書き換えが発生するので、この後もう一度一番上から処理をし直します。
その時、アドレスは既にindex.php
になっていますので、4行目にマッチすることになります。
よって、ここで書き換えが発生することなく[L]
フラグに到達するので、
再処理が発生することなく早々にmod_rewrite
を抜けることができるのですね。
細かい所ですが、無駄な処理を押さえると言う意味では有効なのではないでしょうか。
ところで、さっきの4行目は本当に必要?
ただし、実は先ほどの4行目RewriteRule ^index\.php$ - [L]
は無くても問題ありません。
一見7行目のRewriteRule . /index.php [L]
は全てに適用されるので、
無限ループに陥ってしまいそうですが、そのようなことはなくきちんと終了できます。
なぜなら、2週目では必ずアドレスがindex.php
になるので、
2週目の7行目はindex.php
からindex.php
への変換が行われる事になります。
つまり何も変わらないので、URLの書き換えは発生してないとの判断となり、
ループすることなくそのまま終了します。
つまり「無くても良いけど、入れておけば無駄な処理をしなくて良いよ」と言う事になりますね。
繰り返しとか考えるの面倒なんだけど、良い方法ないの?
こうなると当初考えていたような、その場で処理を終了して次に進んでくれる
(何度も繰り返さない)方法はないのか?と思うわけですよね。
もちろん、あります。
先ほど同様、公式ドキュメントを読むと、
An alternative flag, [END], can be used to terminate not only the current round of rewrite processing but prevent any subsequent rewrite processing from occurring in per-directory (htaccess) context. This does not apply to new requests resulting from external redirects.
RewriteRule Flags - Apache HTTP Server Version 2.4
「[END]
フラグを使うと、それ以降の書き換えは抑制されるよ」
的な事がかいてあります。
つまり、先ほどの例であれば
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule index1\.html /index2.html [END]
RewriteRule index2\.html /index3.html [END]
</IfModule>
このように書けば、以降の書き換えは全て無視されるので…
こんな感じで、ちゃんとindex2.html
にアクセスしようとしていますね。
ただし、この END
フラグは v2.3 からの機能のようですので、
( http://httpd.apache.org/docs/2.4/new_features_2_4.html の mod_rewrite
参照)
v2.2 以前を使用される場合はこのフラグは使えません。
ご注意を!
( @magtaro さん情報ありがとうございます!)
処理が戻ると言うことは…?
ところで[L]
フラグとは関係無いですが、
URLに変更があった場合処理が戻されるということは、次の様なルールを書くと…
RewriteRule ^(.*)$ /$1.html [L]
全てのURLに対し、.html
の拡張子をつけるという事を想定していますが、このように書いてしまうと
Request exceeded the limit of 10 internal redirects due to probable configuration error.
と出てしまいます。
つまり、何度繰り返してもパターンにマッチしてしまうので、index.html.html.html....
と
永遠に書き換えを繰り返してしまい、エラーが発生するのですね。
(デフォルトでは10回リダイレクトを繰り返すとエラーになるようですね)
この場合、末尾に.html
が付与されている場合には処理しないようにすれば
書き換えが多重に発生しないので、
RewriteCond %{REQUEST_URI} !\.html$
RewriteRule ^(.*)$ /$1.html [L]
これならRewriteCond
で末尾が.html
になっているURI以外に対して処理できますね。
結論
[L]
フラグをつけても、.htaccess
のmod_rewrite
は変更がなくなるまで先頭から繰り返し処理するので、
その場で終了するとは限らないよ。ということ。
[END]
をつければその場で終了するように出来るけど、以降の書き換えを全て無視するので注意も必要。
結局、Wordpressみたいに、一度処理された内容のものは最初の方で弾くように
処理を入れられるのであれば、それが一番良いかも。
編集履歴
- 2019/10/18 誤字を修正 ( @TomoakiNagahara さん Thx! )