mod_proxy_balancerとmod_proxy_ajpの組み合わせでスラッシュが重なる
もはやmod_jkの時代は終わったのだのだのだのだ。これからはmod_proxy_ajpによってTomcatへのリバースプロクシーが制御されるのだのだのだのだ。いや、知らんけど。本当にそうならもうmod_jkの開発をやめて開発メンバー全員mod_proxy_ajpに参加するはずやしね。ところでこのmod_proxy_ajpは裏ではmod_jkと同じような動きをしているのかも知れん。mod_jkではworkerというTomcatへの接続先を指定するが、mod_proxy_ajpはajpプロトコルのURLから裏で自動的にworkerを作っているのじゃないかしら。設定ファイル1つ中に別のLocationに対して同じajp://192.168.0.1/webapp1/に飛ばそうとしたらApacheの起動時に「workerが既にある」という警告が出る。
まずは基本設定。以下のようにする。CentOS5.7では/etc/httpd/conf.d/proxy_ajp.confというのを勝手に置いておいてくれるのでそこにこんなことを書くとすぐできる。
<Location "/webapp1/"> ProxyPass ajp://192.168.0.1:8009/webapp1/ </Location>
ところが、応用のために(というか最終的にロードバランスさせないといけないので)以下のような設定にしたら何故かTomcatが受け取るURLがhttp://centos.example.com/webapp1//index.jspとなっている。
<Proxy "balancer://webapp1/"> BalancerMember ajp://192.168.0.1:8009/webapp1/ loadfactor=10 BalancerMember ajp://192.168.0.2:8009/webapp1/ loadfactor=10 </Proxy> <Location "/webapp1/"> ProxyPass balancer://webapp1/ nofailover=Off </Location>
ない頭をいくらひねっても㍉だったので諦めた。URL中のいわゆるPATH_INFOにあたる部分のスラッシュ数は厳密には違いがあるとどこかに書いているのを見た事があるが今のところ動いてはいる模様なので。
さて、インターネット上に数あるApache-Tomcat連携解説サイトにはほぼ必ずと言っていいほど3つの利点を上げている。負荷分散、静的コンテンツ(jpegとか)をApacheで処理させて安定性とパフォーマンスうんぬん、Apacheの細かなアクセス制御うんぬん。大体TomcatのHTTPコネクターは安定性やパフォーマンスでApacheに大きく劣るという論調が主で、だから静的コンテンツはApacheに処理させろという事らしい。
ところがである。負荷分散はbalancerの機能ですぐできるし、アクセス制御はApacheのAllowとDenyで勝手にやってくれれば良いと思うが、静的コンテンツをApacheで処理させてマンセーとか言っているサイトに限って全部Tomcat側に転送している。
<Location "/webapp1/"> ProxyPass ajp://192.168.0.1:8009/webapp1/ </Location>
これだとTomcatのwebapp1が持っている画像やcssは結局Tomcatが処理している事になる。さて、mod_jkの頃はいろいろ細かな制御ができたと聞いているが、ではmod_proxy_ajpで同じ事をするにはどうすれば良いか。まず最初に以下を試した(Tomcat側のWebアプリはStrtus2で開発しており、Struts2フィルターのデフォルト拡張子は.do)、
<LocationMatch "^/webapp1/(.+.do)"> ProxyPass ajp://192.168.0.1:8009/webapp1/ </LocationMatch>
ところが、これだとhttp://centos.example.com/webapp1/init.doにアクセスすると/var/www/html/webapp1/init.doというファイルがないのでLocationMatchディレクティブレベルで404が却ってしまう。解説サイトを漁るとmod_rewriteで[P]を使えと書いているが、後で意味がわからなくなりそうで何となく気分的に嫌だ。
ふと公式の説明を見ていたらProxyPassMatchなるディレクティブが2.2.5から追加になったのだと。CentOS5.7のApacheは2.2.3だが使えた。おーぐれいと。そこで、こうしてみた。
ProxyPassMatch "^/webapp1/(.+.(jpe?g|png|gif|html|css|js)(?.*)?)$" ! <Location "/webapp1/"> ProxyPass ajp://192.168.0.1:8009/webapp1/ </Location>
おーぐれいと。URLが/webapp1/で始まり、その後で文字が最低一文字以上続いた後「.jpg」などドット&特定の文字列になった場合、プロクシーしないという設定だ。大文字のファイル名のリソースがある場合は
ProxyPassMatch "^/webapp1/(.+.([Jj][Pp][Ee]?[Gg]|[Pp][Nn][Gg]|[Gg][Ii][Ff]|[Hh][Tt][Mm][Ll]|[Cc][Ss][Ss]|[Jj][Ss])(?.*)?)$" !
とでもすれば良いだろうか。IgnoreCase的な何かはないのかな。
ところで、こちらでも良い(というか本来はこちらにすべきだと思う)のだが、これだとサーブレット&Webアプリルート(「/」の事)全部に対して設定してあげないといけないので大変である。ここではStruts2のdoという拡張子のURLのみをTomcatに転送している。
ProxyPassMatch "^/webapp1/(.+.do(?.*)?)$" ajp://192.168.0.1:8009/webapp1/$1
ちなみに(.+.do(?.*)?)と意味もなくカッコでくくっているのには意味がある。
ProxyPassMatch "^/webapp1/.+.do(?.*)?$" ajp://192.168.0.1:8009/webapp1/$1
これだと、Tomcatに行くURLはhttp://centos.example.com/webapp1/?hoge=fugaになってしまうのだ。本家サイトのmod_rewriteには書かれていなく、古いバージョンの日本語訳に翻訳者注として書かれていたのだが、キーワード置換をする時に、後で$1とか$2で置き換えるためにはパターン中の最も大きい範囲のカッコで置き換わるらしい。つまり、http://centos.example.com/webapp1/init.do?hoge=fugaにアクセスした時、
1. ProxyPassMatch "^/webapp1/.+.do(?.*)?$" ajp://192.168.0.1:8009/webapp1/$1 ↓ http://centos.example.com/webapp1/?hoge=fuga
2. ProxyPassMatch "^/webapp1/(.+.do(?.*)?)$" ajp://192.168.0.1:8009/webapp1/$1 ↓ http://centos.example.com/webapp1/init.do?hoge=fuga
3. ProxyPassMatch "^/webapp1/.+.do" ajp://192.168.0.1:8009/webapp1/$1 ↓ http://centos.example.com/webapp1/webapp1/init.do?hoge=fuga
3だとカッコが無いためマッチ全体が$1となってしまう。さらにこの3の例では後ろのQUERY_STRINGを引っ掛けていないので$1を入れるとQUERY_STRINGはTomcatにわたらないのである。では$1を外してついでにwebapp1も外して
3'. ProxyPassMatch "^/webapp1/.+.do" ajp://192.168.0.1:8009/ ↓ http://centos.example.com/webapp1/init.do?hoge=fuga
これでめでたしめでたしじゃねと思うかもしれないが、これだとwebapp2がある時に前述の通りワーカーが重複してしまう。それぞれのワーカーに別の設定をしたい時にNGとなるのである。