SinatraをCGIでもThin等のアプリサーバでも使えるようにする書き方

個人的にSinatraLimonadeがマイブームなのですが、Sinatraに関して言えば、CGIで動かす方法は(本家のドキュメントとかでは)あまりフォローされていないようです。国内ではそれなりに記事もあるようなのですが、結局ソースに手を入れてしまう(Sinatraのソースに「Rack::Handler::CGI.run」をベタに書く)か、rackupで実行する方法かが多そう。でも、前者はCGI専用になってしまうのでいまいちだし、後者はシェルが2個実行されるのがどうにも美しくありません(現実的にはあまり問題にならないのかもしれませんが)。

そこでいろいろいじってみたところ、何とか良さげな方法ができました。
まずSinatraのソース。適当です。

require 'rubygems'
require 'sinatra'

set :run, true

get '/?' do
  "hello"
end

get '/test' do
  "hello"
end

ポイントは、getの引数の「'/?'」です。普通は「'/'」と書くところですが、CGIで下記のrewriteを使うと、引数が「''」しかマッチングしなくなります。そこで「'/?'」と書いておくと、''でも'/'でもマッチングするのでした。同様に、'/test/'でも'/test'でもマッチングさせたいときには「'/test/?'」と書けばよいようです。

さて、このままではCGIでは動きません。:runがtrueになっているからです。これはMongrelやThinやWEBrickなどのアプリケーションサーバで動かす場合の設定です。アプリケーションサーバを使う場合はこのままで動きます。

CGIを使う場合のために、以下のようなdispatch.cgiを書きます。

#!/usr/bin/ruby
require 'rubygems'

load 'start.rb'

set :run, false

Rack::Handler::CGI.run Sinatra::Application

なんとなくrubygemsをrequireしていますが、本来はstart.rbで呼び出してくれるので不要なはず。

start.rbをrequireじゃなくてloadしているのは深い意味はありません。

そして:runをfalseに上書きしたあと、RackのCGIハンドラーを呼び出します。これでCGIとして動作するようになります。

ちなみに.htaccessはこんな感じです。

Options +ExecCGI
AddHandler cgi-script cgi

DirectoryIndex dispatch.cgi

<Files start.rb>
deny from all
</Files>

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) dispatch.cgi/$1 [L]

start.rbは直接叩けないようdeny from allにしてあります。また、DirectoryIndexとRewriteRuleで、存在するファイル以外へのアクセスを全てdispatch.cgiで動かすようにしています。

こんな感じで書けば、Sinatraのソース自体はいじらずに、CGIでもアプリサーバでも動かせるようになるはずです。

ただ、本当はSinatraのソースをSinatra::Baseのサブクラスとして書く方が今っぽいのかもしれません。それなら同一プロセスで複数のSinatraアプリを共存させることもできるそうですし。まあでも、classレスですっきりしたソースがSinatraの魅力でもあるので、この辺りは好きずきでどうぞ。