Ruby Freaks Lounge

第24回Rackとは何か(2)Rackの使い

前回の記事では、Rackとは何かについてを、Rackが生まれた背景を交えてご紹介しました。今回は、Rackを実際に使ってみるにはどうすればいいのかを実際に作りながら解説します。

rackupとRack::Builder

前回の記事の最後で、アプリケーションの他にconfig.ruというファイルを用意し、rackupといういうコマンドを使ってアプリケーションを起動しましたが、これについてもう少し詳しく説明したいと思います。

実は、単にRackアプリケーションを起動するためだけであれば、config.ru(rackupファイルと呼びます。拡張子のruはおそらくrackupの略でしょう)は必要ありません。Rackの入門記事等で目にしたことのある方もいるかもしれませんが、以下のようなコードをファイルの末尾に書き加えるとsimple_app.rb単体でアプリケーションを起動することができます。

if __FILE__ == $0
  require 'rack/handler/webrick'
  Rack::Handler::WEBrick.run SimpleApp.new, :Port => 9292
end

rackupから起動した場合と特にかわらない結果になったと思います。前回の構成でrackupコマンドを実行した場合、コマンドの内部でこのような処理をしているからです。このほうが「サーバを起動してアプリケーションを実行している」のがよくわかりますね。では、なぜ最初からこの形ではなく、rackupを使うやり方を紹介したのでしょうか。

一つは、この形式ではサーバ依存のコードが残ってしまっているからです。前回、サーバとアプリケーションの間にRackが入ることによって、自由に組み合わせられるようになったことをRackの利点として説明しました。実際アプリケーションにはサーバ依存の実装はありませんし、rackupを使って簡単にアプリケーションサーバを切り換えられることも見ていただきました。しかし、残念なことに上記のコードでは「どのサーバによってどのように実行されるか」を明示的に記述してしまっているため、⁠些細なことではありますが)サーバを変更する度にアプリケーションのコードを修正する必要があります。これではRackのメリットが活かし切れていません。

また、ThinやMongrelのようにRack本体がハンドラを持っているアプリケーションサーバならこの方法で起動できますし、CGIなどの環境ではむしろこう記述する他はありません。ですが、PassengerのようにWebサーバのモジュールとして動作するタイプのサーバではこの方法で起動することはできませんし、Rack側ではなくサーバ側でRackに対応している場合も同じように書けるとは限りません。MongrelやWEBrickであればrackupコマンドが、ThinやPassengerであればサーバ側で、それぞれ「rackupファイルを読んでアプリケーションを初期化して、適切なハンドラで起動する」ところまではやってくれます。であれば、そちらに任せてしまえばそれぞれの差異を考慮する必要がないので、そうするべきでしょう。

もう一つは、rackupファイルでは、Rack::Builderを通してDSLでRackアプリケーションの構成を記述する仕組みが使えるということです。上記のコードのアプリケーションを起動する部分をRack::Builderを使うように書き換えると、以下のようになります。

if __FILE__ == $0
  require 'rack/builder'
  require 'rack/handler/webrick'
  app = Rack::Builder.new {
      run SimpleApp.new
  }
  Rack::Handler::WEBrick.run app, :Port => 9292
end

Rack::Builderのブロックの中身がrackupファイルと同じになっているのにお気付きでしょうか。rackupコマンドは、rackupファイルの中身をRack::Builderに渡しているわけです。この例ではあまり有難味はありませんが、後述するRack::URLMapやミドルウェアを使うようになると、Rack::Builderを使ってDSLで構成を記述する方がスマートです。

つまり、⁠基本的にはconfig.ruに色々書いて、アプリケーション側にはサーバ依存のコードは書かない」ものだと覚えてください。

Rack::Request/Rack::Response

前回の記事では、⁠リクエストはHashで受けとり、レスポンスはArrayで返す」と言及しました。これはRackの仕様であるため、もちろん従う必要があるのですが、リクエストの扱いなどは概ねどのアプリケーションでも同じなわりに意外と煩雑ですし、レスポンスの形式もちょっとしたミスで上手く動かなかったりしそうですね。

そのため、Rack::RequestRack::Responseというラッパーが用意されています。

前回使ったアプリケーションを上記のクラスを使って書き変えると、以下のようになります。

# coding: utf-8

require 'rack/request'
require 'rack/response'

class SimpleApp
  def call(env)
    req = Rack::Request.new(env)

    body = case req.request_method
           when 'GET'
             '<html><body><form method="POST"><input type="submit" value="見たい?" /></form></body></html>'
           when 'POST'
             '<html><body>何見てんだよ。</body></html>'
           end

    res = Rack::Response.new { |r|
      r.status = 200
      r['Content-Type'] = 'text/html;charset=utf-8'
      r.write body
    }
    res.finish
  end
end

HashやArrayよりは少し扱いやすくなりました。上記のコードでは使っていませんが、クエリの扱いやPOSTデータの受けとり、リダイレクトの制御などもRack::RequestとRack::Responseを使うと簡単にできるようになります。

Rack::MockRequest

Webアプリケーションを書く際、きちんと動くかどうかテストコードを書いて検証することがあると思います。実際にリクエストを投げるスクリプトやSeleniumなどのツールを使ったり、Railsなどではフレームワーク付属のテスト用のライブラリを使ったりすることになると思いますが、Rackではテストを補助するためのライブラリも用意されています。

以下のコードはSimpleAppの挙動をテストする単体テストですが、実際にリクエストを投げるのではなく、MockRequestを使用してサーバにリクエストを投げたときの挙動をテストしています。

# coding: utf-8

require 'simple_app'
require 'test/unit'
require 'rack/mock'

class SimpleAppTest < Test::Unit::TestCase
  def setup
    @app = SimpleApp.new
    @mr  = Rack::MockRequest.new(@app)
  end

  def test_get
    res = nil
    assert_nothing_raised('なんか失敗した') { res = @mr.get('/', :lint => true) }
    assert_not_nil res, 'レスポンス来てない'
    assert_equal 200, res.status, 'ステータスコードが変'
    assert_equal 'text/html;charset=utf-8', res['Content-Type'], 'Content-Typeが変'
    assert_match /見たい?/, res.body, '本文が変'
  end

  def test_post
    res = nil
    assert_nothing_raised('なんか失敗した') { res = @mr.post('/', :lint => true) }
    assert_not_nil res, 'レスポンス来てない'
    assert_equal 200, res.status, 'ステータスコードが変'
    assert_equal 'text/html;charset=utf-8', res['Content-Type'], 'Content-Typeが変'
    assert_match /何見てんだよ。/, res.body, '本文が変'
  end
end

これを利用することで、Railsのコントローラのテストのようにサーバを立ち上げなくてもアプリケーションのテストができますし、実際にRamazeなどはRack::MockRequestを使用してフレームワークのテストライブラリを作っています。また、Rackのテストをより簡単にできるようにするRack::Testのようなライブラリもありますので、Rackアプリケーションを作る場合は利用するとよいでしょう。

Rack::URLMap

Rack::URLMapはRackの「機能」ではなくRack標準添付の「アプリケーション」なのですが、Rack::Builderにも上手く組み込まれていて便利なので紹介しておきます。

このアプリケーションはパスとアプリケーションのマッピングを保持しておき、パスに応じてリクエストを登録してあるアプリケーションに振り分けてくれます。また、Rack::Builderではこのアプリケーションへのショートカットになっているmapという記法が用意されています。

例えば、以下のように使います。

# config: utf-8

require 'simple_app'
require 'rack/lobster'

map '/simple' do
  run SimpleApp.new
end

map '/lobster' do
  # Rackをインストールすると
  # サンプルとして付いてくるアプリケーション
  # ちょっと面白い
  run Rack::Lobster.new
end

config.ruを書き換えたらrackupして、http://localhost:9292/http://localhost:9292/simplehttp://localhost:9292/lobsterそれぞれにアクセスしてみてください。mapで指定した通りに振り分けられ、対応しないパスの時は表示できないことが確認できるはずです。

ちなみに、Rack::URLMapをRack::Builderをからではなく使う場合は、以下のようにします。

urlmap = Rack::URLMap.new( '/simple' => SimpleApp.new, '/lobster' => Rack::Lobster.new )

Rack::URLMapでは複雑なURLの解決はできませんので、通常はアプリケーションやフレームワークの側で実装することになりますが、単機能の簡単なアプリケーションを複数立ち上げたいというような場合には手軽に設定できて便利です。

まとめ

今回は、Rack::Builder、リクエスト/レスポンスの扱い方、テストの仕方、URLのマッピングについてをご紹介しました。ここまでで一通り、Rackアプリケーションを作るにあたって知っておいた方がいいことに触れられたと思いますので、あとは実際にアプリケーションを作って試してみてください。

次回は、Rackの「インターフェースの統一」と並んで重要な仕組みである「ミドルウェア」についてご紹介したいと思います。

おすすめ記事

記事・ニュース一覧