気になりながらもちゃんと中までは見ていなかったRackを触ってみることにした。「Hello, World!」的なコードはすでにあるので何かをRackに載せてみようと思い、最近身のまわりでちょっとしたブームであるgonzuiを選んだ。Rackのバージョンは0.4.0。
gonzuiに含まれるwebインタフェースであるgonzui-serverはWEBrickを使って書かれている。検索エンジンとwebインタフェースの間には中間層のようなものはないようなので、効率は悪くなりそうだがwrapperを入れることにした。最悪のケースだと[gonzui-engine]<>[gonzui-server]<–(webrick api)=(rack spec)–>[rack]=[webrick handler]<>[webrick]のようになる。その代わりに足まわりを変えられる利点が得られる。
とまどったところはいくつかある。
一つはURLMapの使い方で、コードを書き足しながらいろいろ試していたため、ある時点でこんな状態になった。
map "/foo" do run App_foo.new end run App.new
これはrackup用のスクリプトで、最初はrun App.newだったところにmapを足したもの。意図は/fooへのアクセスをApp_fooで処理させるコードを足したつもりだった。結果は以下のようになる。
$ rackup test.ru
.../gems/rack-0.4.0/lib/rack/builder.rb:49:in `to_app': undefined method `call' for {"/foo"=>#<app_foo:0xb7bb7634>}:Hash (NoMethodError)</app_foo:0xb7bb7634>
mapはアプリケーションのリストの最後にハッシュを入れて、そこでURLと振り先のアプリケーションとの対応付けを作る。一方runはアプリケーションのリストの最後に入れる。rackupはこのリストに対してto_appを送り、内部のオブジェクト構造を作る。その際、リストの最後にあるハッシュについてだけは特別な扱いをしていて、URLMapオブジェクト(アプリケーションと呼ぶべき?)に変換する。しかし中間にあるハッシュについてはケアされないため上のような例外が発生する。正しくはAppに対してもmapを使う。
map "/foo" do run App_foo.new end map "/" do run App.new end
もう一つのとまどいはRack::CommonLoggerの動作で、ちょうど上のコードを与えたときにログに記述録されるアクセス先のURLが空になってしまうことがある。
$ rackup test.ru [2008-09-19 11:36:25] INFO WEBrick 1.3.1 [2008-09-19 11:36:25] INFO ruby 1.8.7 (2008-08-11) [i486-linux] [2008-09-19 11:36:30] INFO WEBrick::HTTPServer#start: pid=25843 port=9292 127.0.0.1 - - [19/Sep/2008 11:36:33] "GET / HTTP/1.1" 200 3 0.0007 127.0.0.1 - - [19/Sep/2008 11:36:35] "GET HTTP/1.1" 200 7 0.0007
具体的には/以外のマップへのアクセスの際のログについてベース部分、つまりmapの引数に指定した部分がログの上では消えてしまう。アプリケーションはきちんと動くのですごく困るわけではなかったのだけど、Rackのコードを見るとこの部分(PATH_INFOの内容が出力されている)を意図して削っているため悩んでしまった。結果からいうとRack::Recursiveを使えばこの問題を回避できるようだ。Rack::Recursiveは内部回送をする際に使うものなので、今回は必要ないと思うのだが、このようなわけで入れておくことにした。
ところで、このPATH_INFOを削っているのが、実は、よくできているところの一つだと後で気付いた。というのは上のコードをCGIスクリプトとして実行した場合などでもCGIのPATH_INFOを見て調整してくれるようで、たとえば/cgi-bin/test.ru/fooにアクセスするとちゃんとApp_fooが応答する。
ちょっと困ったのはgonzui-serverが生成するリンクが絶対パスになっていること。gonzuircの:base_mount_pointの値(デフォルトは/)を使って生成されている。Rackにgonzuiを載せるときにもこの値を参照しておいたほうが良さそうなのだけど、WEBrickやMongrelとCGIやFastCGIで、このあたりの関係がややこしいことになる。
たとえば:base_mount_pointを/codesearchとしたとする。導かれる絶対パスは/codesearchとなり、下位のサーブレットへのパスは/codesearch/statなどとなる。このときWEBrickなどでは絶対パスでURLMapを作っておけば特に問題はない。CGIなどでは一般に/cgi-bin/gonzui.cgiがアプリケーションのトップになり、下位サーブレットへは/cgi-bin/gonzui.cgi/statなどでアクセスさせることになる。この場合、HTTPサーバのエイリアス機能によって/cgi-bin/gonzui.cgiを/codesearchに対応付けておかなければならないものの、ひとまずは問題ない。
ここでpublic_htmlに置くことを考える。こういう状況では前述のようなエイリアスを設定するのは難しいことが少なくない。この場合、:base_mount_pointを設定しないでおけばよさそうに思えるのだが、デフォルト値が/であるため/~foo/gonzui.cgiによって生成されるリンクがたとえば/statなどとなってしまう。では:base_mount_pointを/~foo/gonzui.cgiとするとどうか、というと、今度はURLMap側で問題が起きる。前述のように絶対パスでURLMapを作っている場合、この指定は/~foo/gonzui.cgi/~foo/gonzui.cgiがトップであることになってしまう。となるとURLMapを作るときに:base_mount_pointを参照しなければ良さそうなのだが、そうすると今度はWEBrickやMongrelでしぶいことになる。
そんなようなわけで、しょうがないのでadhocに:base_mount_pointを変更して動作するようなwrapperにした。
最後に、CGIやFastCGIでのRackの運用方法がもう一つよくわからなかった。FastCGIについては外部プロセスとして起動してしまうということはもちろん考えられるのだが、動的運用ではどうすると良いのか。rackupを実行するシェルスクリプトを置いておけば動くことは動く。でもなんとなく釈然としない。検索などしてもうまくヒットさせられなかったので、ひとまずはgist:28033のような構成とした。
gonzui-rack.rbの中ではRack::Builderを使ってmapなどしている。Rack::Builderはrackupの中でも使われているが、これは(他のRackミドルウェアと同様に)ネストできるので特に問題ないようだ。
ちなみにabでちょっとだけ比較してみたところ、こんな感じだった。まあ、あまり意味はないかも。
type conc Reqs per Sec Time per Req Time per Req (all) ------------ ---- ------------ ------------ ------------------ WEBrick 1 52.71 18.972[ms] 18.972[ms] WEBrick 3 46.95 63.902[ms] 21.301[ms] Rack/WEBrick 1 54.13 18.475[ms] 18.475[ms] Rack/WEBrick 3 45.85 65.425[ms] 21.808[ms] Rack/CGI 1 3.43 291.723[ms] 291.723[ms] Rack/CGI 3 6.37 470.852[ms] 156.951[ms] Rack/FCGI 1 50.93 19.635[ms] 19.635[ms] Rack/FCGI 3 95.80 31.316[ms] 10.439[ms]