2009年2月23日月曜日

RackでCGI

RackはWebサーバとWebフレームワークをつなぐ最低限のAPIを提供するライブラリで、ハンドラはMongrelやWEBrickだけではなくCGIもサポートしている。ということで、Rackを使えばCGIを書くのにcgi.rbを使う必要がなくなる。また、コードを変更することなくCGIからMongrelなどに切り替えることもできる。

CGIの場合、Webサーバは別で立ち上がっているはずだから、実はrackupは必要ない。とりあえずHello world!をCGIハンドラを使って書いてみるとこんな感じになる。

#!/usr/bin/env ruby
require "rubygems"
require "rack"

class HelloWorld
  def call(env)
    [200, {"Content-Type" => "text/plain"}, "Hello, world!"]
  end
end

Rack::Handler::CGI.run(HelloWorld.new)

あとは普通のCGIと同じように実行権限をつけてcgi-binなどに置けばいい。

ミドルウェアを使う場合は

Rack::Handler::CGI.run(Rack::Session::Cookie.new(HelloWorld.new))

のようにすればいい。RackのミドルウェアはDecoratorパターンで実装されているので、newの引数にアプリケーションをわたすことで何回でもdecorateできる。

WEBrickを使ってテストしてみる。CGIとして実行させたいのでrackupは使えない。

require "webrick"

DOCUMENT_ROOT = File.dirname(__FILE__)

srv = WEBrick::HTTPServer.new(:DocumentRoot => DOCUMENT_ROOT,
                              :BindAddress => "127.0.0.1",
                              :Port => 3000)
srv.mount("/hello_world.cgi",
          WEBrick::HTTPServlet::CGIHandler,
          File.join(DOCUMENT_ROOT, "hello_world.cgi"))
trap("INT"){ srv.shutdown }
srv.start

このスクリプトを実行してから、

http://localhost:3000/hello_world.cgi

にアクセスすればHello, world!と表示されるはず。

(追記)上記のHelloWorldクラスはContent-Typeしかヘッダを返していないため、Rack::Lintを使うとエラーになる。これを解決するにはヘッダにContent-Lengthも追加すればいい。Content-Lengthの値は数値ではなく文字列で渡す必要がある。Rack::LintはアプリケーションがRackのスペックに適合しているかをチェックするミドルウェア。

class HelloWorld
  def call(env)
    body = "Hello, world!"
    [200, {"Content-Type" => "text/plain", "Content-Length" => body.size.to_s}, body]
  end
end

実際のアプリケーションではRack::RequestRack::Responseを使うだろうから、あまり問題にはならないかもしれない。