Alias したディレクトリで RewriteBase を使わずに rewrite する

## Alias したディレクトリで rewrite が動作しない例

例えば、次のように Alias を設定します。

```
DocumentRoot /var/www/html
Alias /ore/sandbox /home/ore/sandbox/html
```

`/home/ore/sandbox/html` には下記の `.htaccess` を配置します。

```
RewriteEngine On

RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]

RewriteRule ^.*$ index.php [NC,L]
```

そして `http://example.com/ore/sandbox/aaa/zzz` のような URL を開いても、意図した通りにリライトされずに *404 Not Found* になります。

## Alias を使わずに動作するとき

上手く動作しない理由を説明するために、Alias を使わずに正しく動作する例を簡単に説明します。

なお `RewriteCond` でファイルやらディレクトリやらの存在チェックをしている部分はあんまり関係ないので説明しません。

Alias を使わないので `http://example.com/ore/sandbox/aaa/zzz` へのリクエストは `/var/www/html/ore/sandbox/aaa/zzz` に解決されます。`/var/www/html/ore/sandbox/` には↑の例と同じ内容の `.htaccess` を配置しているものとします。

- *1*. `mod_rewrite` には `/var/www/html/ore/sandbox/aaa/zzz` というパスが渡される
    - `/var/www/html/ore/sandbox/aaa/zzz`
- *2*. `.htaccess` のディレクトリまでのパスを削除
    - `/var/www/html/ore/sandbox/aaa/zzz` → `aaa/zzz`
- *3*. `RewriteRule` の条件のチェック
    - `^.*$` なのでマッチする
- *4*. パスの書き換え
    - `aaa/zzz` → `index.php`
- *5*. *2*. で除去したパスをくっつける
    - `index.php` → `/var/www/html/ore/sandbox/index.php`
- *6*. ドキュメントルートを削除
    - `/var/www/html/ore/sandbox/index.php` → `/ore/sandbox/index.php`
- *7*. 書き換えられたパスに内部リダイレクト
    - `/ore/sandbox/index.php`

## Alias を使って動作しないとき

Alias を使った場合は `http://example.com/ore/sandbox/aaa/zzz` へのリクエストが `/home/ore/sandbox/html/aaa/zzz` に解決されます。

- *1*. `mod_rewrite` には `/home/ore/sandbox/html/aaa/zzz` というパスが渡される
    - `/home/ore/sandbox/html/aaa/zzz`
- *2*. `.htaccess` のディレクトリまでのパスを削除
    - `/home/ore/sandbox/html/aaa/zzz` → `aaa/zzz`
- *3*. `RewriteRule` の条件のチェック
    - `^.*$` なのでマッチする
- *4*. パスの書き換え
    - `aaa/zzz` → `index.php`
- *5*. *2*. で除去したパスをくっつける
    - `index.php` → `/home/ore/sandbox/html/index.php`
- *6*. ドキュメントルートを削除
    - と言いたいところだがパスのプレフィックスが違うので取り除けない
- *7*. 書き換えられたパスに内部リダイレクト
    - `/home/ore/sandbox/html/index.php`

`/home/ore/sandbox/html/index.php` などという URL は存在しないので *404 Not Found* です。

ちなみに `/home/ore/sandbox/html/index.php` で何か適当なものが表示されるようにしておけば、そいつが表示されます。

```
<Location /home/ore/sandbox/html/index.php>
    SetHandler server-status
</Location>
```

これはキモい。

## RewriteBase を指定したとき

Alias したディレクトリで `mod_rewrite` を使うときは `RewriteBase` を指定する必要があります。

```
RewriteEngine On
RewriteBase /ore/sandbox

RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]

RewriteRule ^.*$ index.php [NC,L]
```

すると、先ほどの「6. ドキュメントルートを削除」以降が次のように変わります。

- *6*. `.htaccess` のディレクトリまでのパスを RewriteBase のパスに書き換え
    - `{/home/ore/sandbox/html/}index.php` → `{/ore/sandbox/}index.php`
- *7*. 書き換えられたパスに内部リダイレクト
    - `/ore/sandbox/index.php`

## RewriteBase を指定せずにどうにかする

[zend-expressive-skeleton](https://github.com/zendframework/zend-expressive-skeleton) を眺めていたら、RewriteBase を使わずにどうにかしているっぽいのを見つけました。

- [public/.htaccess](https://github.com/zendframework/zend-expressive-skeleton/blob/b947082ac6b2d0fc682570a64ffd95ac63326b3d/public/.htaccess#L20-L22)

```
# RewriteCond
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
# RewriteRule(1)
RewriteRule ^(.*) - [E=BASE:%1]
# RewriteRule(2)
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
```

これは次のように動作をします。

- RewriteRule(1) で最初のサブパターンにパスをキャプチャ
    - `$1 := aaa/zzz`
- RewriteCond の左辺
    - `%{REQUEST_URI}::$1` → `/ore/sandbox/aaa/zzz::aaa/zzz`
- RewriteCond の右辺に次のようにマッチ（イメージ）
    - `(/ore/sandbox/)(aaa/zzz)::aaa/zzz`
- RewriteRule(1) が１個目のサブパターンを環境変数に設定
    - `BASE := /ore/sandbox/`
- RewriteRule(2) で `BASE` をくっつけたパスに書き換え
    - `%{ENV:BASE}index.php` → `/ore/sandbox/index.php`
- そのまま内部リダイレクト
    - `/ore/sandbox/index.php`

## さいごに

[symfony-standard](https://github.com/symfony/symfony-standard/blob/22ec5ec332569d44170b0e69c81a238162e7df5d/web/.htaccess#L30-L31) でも似たような方法が使われていたので、わたしが知らなかっただけで割りと有名な方法なのかも。