PHP のプログラムを Docker コンテナで動かそうとして、Apache の mod_rewrite の設定でちょっと苦労したのでメモ。
主題は PHP じゃないので、プログラムは簡単な例にしておく。クエリーパラメータ name
を受け取って、挨拶を返すだけのプログラムだ。
<?php $name = $_GET["name"]; echo "Hello, " . $name . "!" . PHP_EOL; ?>
Docker イメージは php:8.2-apache を使う。
takatoh@apostrophe:myphpapp$ docker run -d --rm -p 80:80 -v .:/var/www/html php:8.2-apache
これで localhost:80 で待ち受けているはず。curl
コマンドで試してみる。
takatoh@apostrophe:myphpapp$ curl http://localhost/hello.php?name=Andy Hello, Andy!
期待通りだ。
つぎに、PHP プログラムに渡すパラメータ name
を、クエリーパラメータではなく、パスの一部にしたい。つまり、http://localhost/hello/Andy
という URL でアクセスしたい。
そこで、つぎのような .htaccess ファイルを書いた。
RewriteEngine on RewriteRule ^hello/(.*)\$ hello.php?name=\$1
それから、Apache で上の .htaccess が有効になるように Dockerfile を書いた。デフォルトでは mod_rewrite 自体が有効になってないんだそうだ。
FROM php:8.2-apache RUN a2enmod rewrite RUN sed -ri -e 's!AllowOverride None!AllowOverride FileInfo!g' /etc/apache2/apache2.conf
mod_rewrite を有効にして、なおかつ、.htaccess ファイルでルールを設定するのを許可している。これを元に Docker イメージをビルド。
takatoh@apostrophe:myphpapp$ docker build -t myphpapp:1 .
コンテナを起動する。
takatoh@apostrophe:myphpapp$ docker run -d --rm -p 80:80 -v .:/var/www/html myphpapp:1
さっきと同じように curl
で試してみよう。
takatoh@apostrophe:myphpapp$ curl http://localhost/hello/Andy Hello, Andy!
うまくいった!もちろんクエリーパラーメータで渡すのでも期待通りに動く。
takatoh@apostrophe:myphpapp$ curl http://localhost/hello.php?name=Andy Hello, Andy!
ところが、パス形式のパラーメータ部分(Andy
の部分)に空白文字を使うとうまく動いてくれない(curl
で URLエンコードする方法がわからなかったので、空白文字の代わりにエンコードした %20
を使っている)。
takatoh@apostrophe:myphpapp$ curl http://localhost/hello/Andy%20Weir <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>403 Forbidden</title> </head><body> <h1>Forbidden</h1> <p>You don't have permission to access this resource.</p> <hr> <address>Apache/2.4.57 (Debian) Server at localhost Port 80</address> </body></html>
クエリーパラメータ形式では大丈夫。
takatoh@apostrophe:myphpapp$ curl http://localhost/hello.php?name=Andy%20Weir Hello, Andy Weir!
ブラウザでも試したけど同じ結果。さて、困った。
結論を先に書くと、RewiterRule
の最後に B
フラグをつければ解決する。Apache の公式ドキュメントにはちゃんと書いてある。が、参考にした日本語の解説ページの中には書いてあるページはなかった。探し方が悪いのかもしれないけど。
ともかく、.htaccess ファイルをつぎのように書き換えた。
RewriteEngine on RewriteRule ^hello/(.*)\$ hello.php?name=\$1 [B]
再度試してみる。
takatoh@apostrophe:myphpapp$ curl http://localhost/hello/Andy%20Weir Hello, Andy Weir!
OK!!!
一件落着。
後々のために、Apache のドキュメントから解説を引用しておこう(DeepL 翻訳)。
[B] フラグは、変換を適用する前に英数字以外の文字をエスケープするよう RewriteRule に指示します。
mod_rewrite は URL をマッピングする前にエスケープを解除しなければならないので、 後方参照は適用される時点でエスケープされません。B フラグを使うと、後方参照中の英数字以外の文字がエスケープされます
‘x & y/z’という検索語が与えられた場合、ブラウザはそれを’x%20%26%20y%2Fz’としてエンコードし、リクエストを’search/x%20%26%20y%2Fz’とします。Bフラグがない場合、このリライトルールは’search.php?term=x & y/z’にマップされますが、これは有効なURLではないため、search.php?term=x%20&y%2Fz=としてエンコードされ、意図されたものではありません。
この同じルールにBフラグを設定すると、パラメータは出力URLに渡される前に再エンコードされ、結果として/search.php?term=x%20%26%20%y%2Fzに正しくマッピングされます