CGI から Mongrel まで、Rack で Web アプリを Web サーバから抽象化する

by tanabe on February 10, 2008

Rack ってなに?

Rack は Web サーバと Ruby プログラムや Ruby で書かれた Web アプリケーションフレームワークとの間に、最小限のインターフェースを提供します。

http://rack.rubyforge.org/

Rack のインストール

gem install rack

Rack の簡単な始め方

Rack を使うには、まず call されるアプリケーションを書きます。call メソッドを定義し、引数に env を取ります。

 # app.rb
 require 'rack'
 class TinyCaller
   def call(env)
     [200, {'Content-Type' => 'text/html'}, ["Hello, World."]]
   end
 end 

続いて、Rack を使うための DSL ファイルとなる .ru ファイルを作成します。

 # tiny_caller.ru
 require 'app'
 run TinyCaller.new

Rack を起動するには、コマンドラインから次のように入力します。-s オプションに webrick を指定すると WEBrick が Web サーバとして使用されます。

 rackup -s mongrel -p 10080 tiny_caller.ru

Rack の基本的な使い方

Request クラス

Rack では、call されたときに引数として渡される env に環境変数一式がハッシュとして入っています。Request クラスはこの env をクラスのようにアクセスできるように補助してくれ、自然な形でのリクエストパラメタの利用が可能となります。

  • Rack#GET は QUERY_STRING を parse したハッシュを返す
  • Rack#POST は FORM からのパラメタを parse したハッシュを返す
  • クエリパラメタについては、基本的には Request.new(env).params で取得可能。ハッシュとしてアクセスできる。(実は syntax sugar で、実装としては上記 GET と POST を利用している)
  • Request#[](key)
  • Request#[]=(key, value) は Request#params(key), Request#params=(key, value) へのショートカット
  • Request#env で環境変数へハッシュのようにアクセス可能
  • Request#cookies でクッキーへハッシュのようにアクセス可能
  • Request#xhr? XMLHttpRequest として call されたなら true
  • Request#url 呼び出した URL を String として返す
  • Request#fullpath ルートからの相対パスをフルパスで返す
  • Request#get?, Request#post?, Request#put?, Request#delete? HTTP Request のメソッドが何形式かを判定し、boolean を返す

Response クラス

Response は HTTP Response の構築をサポートするクラスです。なので、基本的には、

  • HTTP status
  • header
  • body
を組み立てれば任務完了となります。

Response#initialize(body=[], status=200, header={}, &block)

header には、自動的に "Content-Type" => "text/html" が追加されます。body は、文字列か文字列の集合を与えます。(厳密には to_str できれば何でもいいらしい)

Response#write と Response#finish

Response#finish を行うまで、 Response#write された内容が蓄積されます。body としたい内容を Response#write していき、最後に finish を呼び出し結果を確定させます。

その他
  • [](key), []=(key, value) は header への syntax sugar
  • Response#set_cookie(key, value)
  • Response#delete_cookie(key, value={})

Response と Request を使った簡単なサンプル

 # app.rb
 class TinyCaller
   def call(env)
     req = Rack::Request.new(env)
     res = Rack::Response.new
     res.write req.env.map {|k,v| "<li>#{k}: #{v}</li>"}.sort.join("\n")
     res.finish
   end
 end

Response をブロックで

 # app.rb
 class TinyCaller
   def call(env)
     req = Rack::Request.new(env)
     Rack::Response.new.finish do |res|
       res.write "<h2>Rack Environment Variables</h2>"
       res.write req.env.map {|k,v| "<li>#{k}: #{v}</li>"}.sort.join("\n")
     end
   end
 end

Rack でセッションを使う

Rack でセッションを使うには、

  • ru ファイルでセッションを使うことを宣言
  • アプリケーションでは、Request#env['rack.session'] を通して利用(デフォルト)
とするのが簡単です。

Rack では、下記のように use を使って ru ファイルにミドルウェアを積んでいくことで簡単に機能強化ができるようになっています。

 # tiny_caller.ru
 require 'app'
 use Rack::Session::Cookie
 run TinyCaller.new
 # app.rb
 class TinyCaller
   def call(env)
     req = Rack::Request.new(env)
     session_data = req.env['rack.session']
     session_data['counter'] ||= 0
     session_data['counter'] += 1
     Rack::Response.new.finish do |res|
       res.write "<li>count: #{session_data['counter']}</li>"
     end
   end
 end

細かい設定をしたい

ru ファイルで use する際にオプションを細かく指定することも可能です。

 use Rack::Session::Cookie, :key => 'rack.session',
                            :domain => 'foo.com',
                            :path => '/',
                            :expire_after => 2592000
 All parameters are optional.
via) http://rack.rubyforge.org/doc/classes/Rack/Session/Cookie.html

よりセキュアにしたい

今のところ、Rack の Session モジュールには Cookie しかありません。今後、サーバサイドで状態を管理するものがリリースされるのを期待するか、自分で作りましょう。(永続化は Rack のプロジェクトのスコープ外である可能性はあります。そこまでやってしまうと、Web フレームワーク化していくので。)

URI によってルーティングしたい

Rack::URLMap を使えば実現できます。ru ファイルでは、URLMap へのショートカットである map が提供されています。

 # tiny_caller.ru
 require 'app'
 require 'another_app'
 map "/" do
   use Rack::Session::Cookie
   run TinyCaller.new
 end
 map "/app2" do
   run AnotherCaller.new
 end

HTML エスケープする

Rack::Utils#escape_html(string) を使います。

認証をしたい

Rack::Auth でできるんだろうけど、試していないので詳細不明。

もっと知りたい

当初、誤って「 CGI から Rails まで」としてましたが、Rails の Handler は TODO 扱いですね。現状は、対応していません。(Rails 自体の script/server で WEBrick と mongrel は簡単に切り替えられるので、あまりここは支障ないですが。)



Rack について調べながら、Rack のコードを読んでたんだけど、とても読みやすかった。全体の構造もわかりやすいし、各ファイルもコンパクトでざっと見ると内容が想像しやすい。

Rack の説明を読んでも使い方がよく分からない人は、(ぼくの説明が不十分or不適切か、)Web アプリに関する基本的なあれこれを理解したほうが早いと思うので Ruby のまじめな名著「Ruby アプリケーションプログラミング」を一度読んでみるのがおすすめ。Ruby で CGI アプリケーションを書くための解説の章で Web アプリケーションの基本的な仕組みについても丁寧に解説されていて、非常にわかりやすい。内容は少し古いけど、基本は変わらないので今でも十分使える。

Rubyアプリケーションプログラミング
前田 修吾 まつもと ゆきひろ やまだ あきら 永井 秀利
オーム社 (2002/04)
売り上げランキング: 12952