mod_proxyを経由するとURIが勝手にデコードされる

フロントに Apache を置き、バックエンドの Tomcat に mod_proxy_ajp 経由でリクエストを転送するという一般的な構成において、日本語では探しても余り見つからなかったネタがありましたので書いておきます。

リクエスURIに「URLエンコードされた文字」を含む場合、mod_proxy はこれらの文字をデコードした上で、バックエンドに転送します。

この動作は、通常のWebアプリでは特に問題にはならないと思います。

しかし、次のようなリクエスURIを考えてみましょう。

/path/to/foo%3Bbar

これをデコードすると次のようになります。

/path/to/foo;bar

ご承知の通り、URIセミコロンが含まれていた場合、Tomcat はそれ以降の文字を別セグメントと認識してしまいます。このため、mod_proxy_ajp 経由の Tomcat では、

request.getPathInfo()   -> /path/to/foo
request.getRequestURI() -> /path/to/foo;bar

と、getPathInfo() ではセミコロン以前の部分しか取得できません。無論、mod_proxy_ajp を経由せず、Tomcat で直にリクエストを受け取ると、

request.getPathInfo()   -> /path/to/foo;bar
request.getRequestURI() -> /path/to/foo%3Bbar

となります。

これについては、すでに mod_proxy のバグとしてレポートがなされていて、ProxyPassディレクティブへの "nocanon" というパラメータの追加という形で回避できるよう修正されています。この修正は、Apache 2.2.7(非公開)以降で有効なのですが、mod_proxyのリファレンスの日本語には無く、英語版にしか記述がありません。

この英語版の該当部分を見ると以下のようになっていて、

Normally, mod_proxy will canonicalise ProxyPassed URLs. But this may be incompatible with some backends, particularly those that make use of PATH_INFO. The optional nocanon keyword suppresses this, and passes the URL path "raw" to the backend. Note that may affect the security of your backend, as it removes the normal limited protection against URL-based attacks provided by the proxy.
http://httpd.apache.org/docs/2.2/en/mod/mod_proxy.html#proxypass

「オプションの"nocanon"というキーワードによって『これ(URLをデコードしちゃう処理 = canonicalise)』を抑制することができ、バックエンドには"生の"URLが渡されるようになる」ことが分かります。

つまり、今まで httpd.conf に、次のように設定していた所を、

ProxyPass /context/ ajp://192.168.0.x:8009/

次のように変更すればOKです。

ProxyPass /context/ ajp://192.168.0.x:8009/ nocanon

これにより、mod_proxy_ajp を経由しても Tomcat で直にリクエストを受け取った時と同じように動作します。

ちなみに、ProxyPass への nocanon キーワードの追加は 2.2.7 で取り込まれているのですが、mod_proxy_ajp などのモジュールでは無効だったようで、これまた、バグレポートされています。なお、これもすでに修正されていて、2.2.9 に取り込まれています。

2010/1/21追記
mod_proxy_ajp経由の getRequestURI() の戻り値はセミコロンを含むのが正解でしたので修正しました。また、Tomcat直アクセス時の getRequestURI() の戻り値に関して、URLエンコードの記述が間違っていましたのでこちらも修正しました。