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

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

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

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

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

```apache: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` にアクセスすると、最終的に開かれるファイルは何になるでしょうか。

```apache
<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](https://qiita-image-store.s3.amazonaws.com/0/92484/2e33c3d0-c6c3-8441-2b8e-ba857639f36f.png)

~~「アイエエエ!? `index3.html`!? `index3.html`ナンデ!?」~~
「なぜか `[L]` を無視して２つ目のルールも適用されて、`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](https://httpd.apache.org/docs/current/rewrite/flags.html)

「この処理は一度書き換えを行った後、そのアクセスを行えるかどうかを判別する為にURLパースエンジンに書き換えたものを戻すよ」
と書かれています。

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

今回の場合は、

{`index1.html`を`index2.html`に変換して、[L]フラグにより変換を一度終了｝
↓
{URLが変更されたので処理が戻る}
↓
{二週目は`index2.html`になっているので、一つ目のルールを飛ばして二つ目に適合して`index3.html`に変換される}
↓
{URLが変更されたので処理が戻る}
↓
{三週目は`index3.html`になっているので、どちらのルールにも適合しない}
↓
{URLに変更がなかったので処理が進む}


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


## じゃあ、 .htaccess ファイルで [L] フラグは無意味ってこと？
結局もう一度処理されてしまうのであれば、`[L]`のフラグは無意味だと言うことになるのでしょうか？

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

```apache: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]`がついていますね。

ここで、７行目の`RewriteRule . /index.php [L]`が適用されたパターンを考えてみましょう。
7行目では、ここまで到達したアドレスを全て`/index.php`へ書き換えています。
で、書き換えが発生するので、この後もう一度一番上から処理をし直します。
その時、アドレスは既に`index.php`になっていますので、4行目にマッチすることになります。

よって、ここで**書き換えが発生することなく**`[L]`フラグに到達するので、
再処理が発生することなく早々に`mod_rewrite`を抜けることができるのですね。

細かい所ですが、無駄な処理を押さえると言う意味では有効なのではないでしょうか。

### ところで、さっきの4行目は本当に必要？
ただし、実は先ほどの4行目`RewriteRule ^index\.php$ - [L]`は無くても問題ありません。

一見７行目の`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](https://httpd.apache.org/docs/current/rewrite/flags.html)

「`[END]`フラグを使うと、それ以降の書き換えは抑制されるよ」
的な事がかいてあります。

つまり、先ほどの例であれば

```apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule index1\.html /index2.html [END]
RewriteRule index2\.html /index3.html [END]
</IfModule>
```

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

![ss2.png](https://qiita-image-store.s3.amazonaws.com/0/92484/6e6532b2-3684-a2ae-25ae-9573dad44880.png)

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

ただし、この `END` フラグは v2.3 からの機能のようですので、
( http://httpd.apache.org/docs/2.4/new_features_2_4.html の `mod_rewrite` 参照)
v2.2 以前を使用される場合はこのフラグは使えません。
ご注意を！
( @magtaro さん情報ありがとうございます！)

## 処理が戻ると言うことは…？

ところで`[L]`フラグとは関係無いですが、
URLに変更があった場合処理が戻されるということは、次の様なルールを書くと…

```apache
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`が付与されている場合には処理しないようにすれば
書き換えが多重に発生しないので、

```apache
RewriteCond %{REQUEST_URI} !\.html$
RewriteRule ^(.*)$ /$1.html [L]
```

これなら`RewriteCond`で末尾が`.html`になっているURI以外に対して処理できますね。

## 結論
`[L]`フラグをつけても、`.htaccess`の`mod_rewrite`は変更がなくなるまで先頭から繰り返し処理するので、
その場で終了するとは限らないよ。ということ。

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

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

# 編集履歴
- 2019/10/18 誤字を修正 ( @TomoakiNagahara さん Thx! )