何事もなかったように記事再開。

で、何が困ったかというと、apache httpd-2.2.4に添付のmod_proxy_balancerの挙動。

mod_proxy_balancerについてはこちら。要はmod_proxy系の各種モジュールでproxy先をバランシングできるわけですな。mod_proxy_balancer自体はかなり汎用性のあるモジュールなんだけど、今回困った用途はmod_proxy_ajpとの組み合わせ、つまりmod_jk(2)の代用として使ったときのこと。

たとえばweb層をapache(+mod_ajp +mod_proxy_balancer)の1台、ap層をtomcatの2台構成にしたとき、かつstatefulに振り分けをする必要があって、かつtomcatでセッションレプリケーションが使えない時にstickysessionの機能を使う。

httpd.conf中ではこんなかんじ。

 ProxyPass /webapp/ balancer://testcluster/webapp/ stickysession=ss
 <Proxy balancer://testcluster>
        # r0
        BalancerMember ajp://192.168.100.10:8009 loadfactor=1 route=r0
        # r1
        BalancerMember ajp://192.168.100.11:8009 loadfactor=1 route=r1
 </Proxy>

この設定はssという変数中にr0という文字列が含まれた場合(正しくはss=hogehhuga.r0 のようにピリオド+文字列で終わった場合)にはajp://192.168.100.10:8009に、ajp://192.168.100.11:8009にr1という文字列が含まれた場合にはajp://192.168.100.11:8009にproxyするというもの。変数とは具体的にはCookie、CGI環境変数、urlが利用できるスグレモノだ。

これをtomcatが付加してくれるjsessionidとjvmRouteと組み合わせるとstickysessionを実現できるようになる。2台のtomcatのjvmRouteにそれぞれr0, r1を指定すると、UAがCookieを使える設定になっていればCookieにJSESSIONID=hanamogerahogehoge.r0、Cookieが使えないUAにはURLパラメータとして;jsessionid=gegehogegehoge.r0といった具合に値を付加してくれる。ここでhttpd.confで以下のように設定しておけば、JSESSIONIDにr0が入っていればtomcat(r0)に、r1が入っていればtomcat(r1)にajp接続してくれる。一つのsessionは同じコンテナに接続される、という仕組み。

 ProxyPass /webapp/ balancer://testcluster/webapp/ stickysession=JSESSIONID
 <Proxy balancer://testcluster>
        # r0
        BalancerMember ajp://192.168.100.10:8009 loadfactor=1 route=r0
        # r1
        BalancerMember ajp://192.168.100.11:8009 loadfactor=1 route=r1
 </Proxy>

ただし、stickysession=JSESSIONIDではJSESSIONIDが大文字の場合にしかmatchしない。しかも良くないことにtomcatはCookieではJSESSIONIDと大文字、urlではjsessionidと小文字で変数名を指定する。たとえばCookieを有効にしたUAのみを対象にしたサイトでは問題ないが、Cookieの使えない携帯電話用サイトなども混在差せる場合これでは対応できない。しかも今の仕様ではstickysessionを複数指定することもできないようだ。

とはいえ、この仕様はapacheのbugzillaにも登録されており、一応不具合として認識はされているようだ。該当のbug。とりあえずここのpatchを、httpd-2.2.4のmod_proxy_balancer.cに当たるようにしたものがこれ(ほとんど変わらないけど)。

 --- mod_proxy_balancer.c.orig   Wed May 23 11:09:16 2007
 +++ mod_proxy_balancer.c        Wed May 23 11:58:50 2007
 @@ -112,9 +112,18 @@
                              const char *name)
  {
      char *path = NULL;
 +    char *session_id = NULL;
 +    int  i;
 
 -    for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
 -        path += strlen(name);
 +    session_id= apr_pstrdup(pool, name);
 +    /* Change 'JSESSIONID' to 'jsessionid' to match the value in the url */
 +    if (isupper(name[0])) {
 +      for (i=0;i<=strlen(session_id);i++)
 +       session_id[i] = tolower(session_id[i]);
 +    }
 +
 +    for (path = strstr(url, session_id); path; path = strstr(path + 1, session_id)) {
 +        path += strlen(session_id);
          if (*path == '=') {
              /*
               * Session path was found, get it's value

このpatchを当てることで、とりあえず無邪気にstickysessionを小文字でもmatchするようにしてくれる。本来の挙動(大文字と小文字を厳密に区別する)を期待する場合にはpatchを当ててはいけない。

本来この設定と挙動はどうあるべきか、という議論は、このへんから始まる一連のthreadで話合われているようなので、次回のリリース(httpd-2.2.5?)では何らかの実装の変更があるだろう。