Rack を使って Web サーバで統一されたインターフェイスの利用する

http://rack.rubyforge.org/
先日登場した Rack というライブラリを使うと、Web サーバごとに同一のインターフェイスを利用できるようになります。通常、Ruby で Web アプリケーション を動作させるとき、cgi なのか、fcgi なのか、もしくは mongrelwebrick を使うのか、ということを考えなければなりません。cgi のみで動作決めうち、など良いのですが、ガワである Web アプリケーションフレームワークや一般に配布するアプリケーションなど、既存のフレームワークを用いずに作るときは、さまざまな動作環境でも動くように考慮しなくてはなりません。
たとえば既存のフレームワークである Rails や Camping はそれぞれ個別に cgi/fcgi/mongrel/webrick などの対応を行っています。しかし Rack で定義されているインターフェイスを使うことによって、どんな環境で動かすかを意識せずに開発をすることが可能になります。
それでは単純な HelloWorld アプリケーションの例です。

require 'rubygems'
require 'rack'
include Rack

hello_app = Proc.new do |env|
  Response.new.finish do |res|
    res.write 'hello world!'
  end
end

Handler::Mongrel.run hello_app, :Port => 9202

このコードを動かすと、mongrel が port 9202 で立ち上がり、http://localhost:9202/ にアクセスすると hello world! と表示されているはずです。で、着目すべきは最後の

Handler::Mongrel.run hello_app, :Port => 9202

の行で、ハンドラを WEBrick に変更し

Handler::WEBrick.run hello_app, :Port => 9202

起動することで、WEBrick が立ち上がり hello world! が表示されるはずです。また Handler は現時点で CGI/FastCGI/Mongrel/WEBrick が用意されており、それぞれを切り替えるだけで対応させることができます。簡単ですね。

一歩進んだ利用方法

ハンドラ起動時に渡している hello_app は別に Proc オブジェクトじゃなくとも、call メソッドが定義されていれば何でも OK です。そしてブラウザからのリクエスト発生時に call メソッドの引数に環境変数 env が渡され呼び出されます。そこで Response のインスタンスを結果として返せば処理は OK です。
なおライブラリに標準として

  • File
  • Lint
  • CommonLogger
  • Cascade
  • URLMap
  • ShowExceptions
  • Builder

などの call メソッドを備えたクラスがあり、このうちの大半が Proxy として、コンストラクタの引数のインスタンスの call メソッドを呼んでいきます。
たとえば、現在はエラーが起きたときは何にも処理を行ってませんが、エラーが起きたときに html で backtrace を表示する ShowExceptions を使うとすると

hello_app = Proc.new do |env|
  foo # error
  Response.new.finish do |res|
    res.write 'hello world!'
  end
end
Handler::Mongrel.run ShowExceptions.new(hello_app), :Port => 9202

のようなコードになります。
画像
な画面が表示されますね。また、アクセス毎にログを表示したいならその上に CommonLogger をかぶせます。

Handler::Mongrel.run CommonLogger.new(ShowExceptions.new(hello_app)), :Port => 9202

で、アクセスすると

192.168.155.200 - - [07/Mar/2007 16:30:52] "GET / HTTP/1.1" 500 55069 0.3931

のようなログが表示されます。
また URL のマッピングには URLMap クラスを使います。本当は Builder クラスでできるはずなのですがなんか挙動が変なので URLMap クラスで説明します。

hello_app = Proc.new do |env|
  Response.new.finish do |res|
    res.write 'hello world!'
  end
end

reverse_app = Proc.new do |env|
  req = Request.new(env)
  if req.GET['word']
    body = CGI::escapeHTML(req.GET['word'].reverse)
  else
    body = 'set query param word'
  end
  Response.new.finish do |res|
    res.write body
  end
end

app = URLMap.new([
  ['/reverse', reverse_app],
  ['/', hello_app]
])
Handler::Mongrel.run app, :Port => 9202

こんなように、URLMap のコンストラクタで path, app の配列を渡してやるとそれぞれにマッピングされ、/reverse?word=takeyabuyaketa にアクセスすると「atekayubayekat」が、/ にアクセスすると「hello world!」が表示されると思います。

というわけでかなり簡単に扱えて、かつ最小限の機能のみを押さえているので、いちいち Rails 使うほどじゃないけど cgi/fcgi/mongrel などなどにはとりあえず対応しておきたい、なんてひとは使ってみてはどうでしょうか。