CGIでRailsをまともに動かす

普通にRuby on RailsをCGI(dispatch.cgi)で動かすと遅すぎてやってられませんが、gateway.cgiを使うと、そこそこの速度で動くようになります。


最初に仕掛けを紹介してしまうと、1回目のアクセスがあったときに常駐プロセスを起動し、2回目以降のアクセスはその常駐プロセスに処理させるようになっています。CGI自体は常駐プロセスに処理を投げるだけなので軽い、というわけです。ただし、1回目のアクセスは通常通りCGIで動作させたくらいの遅さです。
常駐プロセスは一定時間アクセスがないと自動的に終了するので(次のアクセスがあったときにまた起動する)、いろいろ制限のある環境でも使える、かもしれません。


さて、そのgateway.cgiですが、Railsの標準パッケージの中に含まれています。まだexperimentalらしいですが、多少パッチを当てると動きます。
使い方は↓こんな感じです。

# railsとfcgiをインストールする
$ gem install rails
$ gem install fcgi

# railsが/usr/lib/ruby/gems/1.8/rails-2.0.2にあるとすると…
$ RAILS_DIR=/usr/lib/ruby/gems/1.8/rails-2.0.2

# 新しいrailsアプリケーションを作る
$ rails gateway
$ cd gateway

# log/drb_gatewayディレクトリを作って、CGIのプロセスが書き込めるようにしておく
$ mkdir log/drb_gateway
$ chmod 777 log/drb_gateway

# スクリプトをコピーしてくる
$ cp $RAILS_DIR/dispatchers/gateway.cgi public/
$ cp $RAILS_DIR/lib/commands/ncgi/listener script/
$ cp $RAILS_DIR/lib/commands/ncgi/tracker script/
$ chmod 755 public/gateway.cgi

# パッチを当てる
$ patch -p1 < gateway.patch

# 試しに動かしてみる
$ ruby script/generate controller Test index

# WEBrickの手抜きサーバーで
$ ruby gateway-test.rb
  • gateway.patch:
diff -Nurp gateway.orig/public/gateway.cgi gateway/public/gateway.cgi
--- gateway.orig/public/gateway.cgi	2008-02-09 22:04:17.000000000 +0900
+++ gateway/public/gateway.cgi	2008-02-09 22:05:53.000000000 +0900
@@ -1,4 +1,4 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 
 require 'drb'
 
diff -Nurp gateway.orig/script/listener gateway/script/listener
--- gateway.orig/script/listener	2008-02-09 22:04:30.000000000 +0900
+++ gateway/script/listener	2008-02-09 22:02:21.000000000 +0900
@@ -14,6 +14,8 @@ class RemoteCGI < CGI
     self.env_table = env_table
     self.stdinput = input || StringIO.new
     self.stdoutput = output || StringIO.new
+    $stdin = self.stdinput
+    $stdout = self.stdoutput
     super()
   end
 
  • gateway-test.rb:
#!/usr/bin/env ruby
require 'webrick'
config = {
	:DocumentRoot => '.',
	:BindAddress => '127.0.0.1',
	:Port => 3000,
}
server = WEBrick::HTTPServer.new(config)
server.mount('/', WEBrick::HTTPServlet::CGIHandler, "#{Dir.pwd}/public/gateway.cgi")
trap('INT')  { server.shutdown }
trap('TERM') { server.shutdown }
server.start

Apacheで動かすときはpublic/.htaccessのdispatch.cgiをgateway.cgiに書き換えておく必要があります。



おもしろいのはその動作方法で、常駐プロセスとCGIの通信にはUNIXドメインソケットとdRubyが使われています。一度アクセスするとlog/drb_gatewayディレクトリにソケットが作られます。
常駐プロセスはFastCGIをエミュレーションしており、Railsから見るとFastCGIで動作しているように見えるようです。


このやり方自体はRails以外にも応用できそうです。CGIしか使えない環境を想定する場合は、この方法を実装してみると劇的に体感速度が上がるかもしれません。