Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

.htaccess に RewriteRule 書くときは、[L]フラグをつけてもそこで終了しないかもよ?って話。

More than 1 year has passed since last update.

.htaccessRewriteRule書くときに、安易に[L]フラグを使うと思ったように動かないかもしれない。
仕組みを理解して使えば単純な事なんですがね!

.htaccess でアクセスを振り分けたい!

Apache HTTP サーバーを使っていると、割とよく使う .htaccess ファイル。
特にアクセスをうまく振り分ける為に、 mod_rewrite を使用してる方も結構いらっしゃいますよね。

なんて言ったって、天下の Wordpress 様が使っておられますから。
Wordpress では、アクセスを基本的に index.php に集めて、そこから URL 解析をして特定のページに振り分けてますよね。

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

それぞれの文法については、既に多くの解説記事が 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が開かれる。」
ということになります。

では実際に実験してみましょう。

ss.png

「アイエエエ!? 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パースエンジンに書き換えたものを戻すよ」
と書かれています。

と言う事は、書き換えが行われた場合は処理を戻すということなので、
.htaccessmod_rewriteについても、もう一度処理されるということですね!

今回の場合は、

{index1.htmlindex2.htmlに変換して、[L]フラグにより変換を一度終了}

{URLが変更されたので処理が戻る}

{二週目はindex2.htmlになっているので、一つ目のルールを飛ばして二つ目に適合してindex3.htmlに変換される}

{URLが変更されたので処理が戻る}

{三週目はindex3.htmlになっているので、どちらのルールにも適合しない}

{URLに変更がなかったので処理が進む}

と言うように何度も処理が繰り返されていたのですね。

じゃあ、 .htaccess ファイルで [L] フラグは無意味ってこと?

結局もう一度処理されてしまうのであれば、[L]のフラグは無意味だと言うことになるのでしょうか?

いえ、そういうことではないようです。
もう一度 Wordpress の.htaccessファイルを見てみましょう。

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>

このように書けば、以降の書き換えは全て無視されるので…

ss2.png

こんな感じで、ちゃんとindex2.htmlにアクセスしようとしていますね。

ただし、この END フラグは v2.3 からの機能のようですので、
( http://httpd.apache.org/docs/2.4/new_features_2_4.htmlmod_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]フラグをつけても、.htaccessmod_rewriteは変更がなくなるまで先頭から繰り返し処理するので、
その場で終了するとは限らないよ。ということ。

[END]をつければその場で終了するように出来るけど、以降の書き換えを全て無視するので注意も必要。

結局、Wordpressみたいに、一度処理された内容のものは最初の方で弾くように
処理を入れられるのであれば、それが一番良いかも。

編集履歴

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away