Rack - Ruby のフレームワークと Web サーバを繋ぐ

Written by @dr_taka_n at 2009/07/05 21:42 [, , ]

Rack とは

Rack とは、(Web) アプリケーションと Web サーバーを繋ぐインターフェイスになる。

この Rack だが、ほぅ、そんな考え方もあるね、というレベルでの認知だったが、Rails が v2.3 から Rack 対応となったことも受け、どんなものなのか見てみることにした。

本家である、Rack: a Ruby Webserver Interface のサイトの言葉を借りると、

Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks.

Rack は Ruby をサポートする Web サーバ(達)と Ruby のフレームワーク(達)の間の最低限のインターフェイスを提供する。

とある。

現在、Ruby で書かれている Web アプリ用のフレームワークは有名な Rails を筆頭に結構な数が出てきている。また、Ruby のための Web サーバも結構な数が出てきている。フレームワークとそれを動作させる Web サーバの組み合わせは増え続けており、それぞれが相手先に合わせた対応を行っていくというのは、結構シンドイことになる。

そこで、フレームワークとそれを動作させる Web サーバの間を取り持つインターフェイスとして、Rack が登場した。お互いに間を取り持つ Rack を意識して開発をしていれば、事はスムーズに進むはず、ということのようだ。

Web でのエンドユーザと Web サーバのやり取り自体は単純である。 Web サーバは、リクエストを受けとり、レスポンスを返す、つまり、「Web アプリって、要するにリクエストをレスポンスに変換するだけの関数だよね」というのが Rack の基本思想のようだ。

以下のサイトにて 5 分でわかるように親切に説明を行ってくれている。

Rack プロトコル

では、具体的にどのようにフレームワークと Web サーバの間の取り持ちを Rack は行おうとしているのだろうか。

先程の「Web アプリって、要するにリクエストをレスポンスに変換するだけの関数だよね」という Rack の基本思想にあるように、

  • 環境変数(のハッシュ)を受け取って(リクエスト)
  • ステータスコードと HTTP ヘッダと HTTP ボディを返す(レスポンス)

というインターフェイスの実装になっている。

前者の環境変数は、String オジェクトをキーとする Hash オブジェクトになっている。直接操作もできるが、Rack::Request でラップしてあげると扱い易い。

req = Rack::Request.new(env)

後者のレスポンスは、3つの要素からなる配列となっている。

  • 第1要素
    • HTTP ステータスコード
  • 第2要素
    • HTTP レスポンスヘッダー
  • 第3要素
    • HTTP ボディ

Rack::Response で扱えるようになっている。

Rack を使用して作成したアプリケーションが Rack のプロトコルに正しく従っているかどうかは、Rack::Lint というミドルウェアをRack(棚) に組み込むことでチェックすることができるようだ。

use Rack::Lint

Rack を試してみる

実際に試してみないと何のことかよくわからない。。試してみる。

まずは、事前準備として、Rack のインストールから。

$ sudo gem install rack


$ gem list rack

*** LOCAL GEMS ***

rack (1.0.0)

お決まりの “Hello Wolrd!” を表示する Web アプリケーションを Rack で書いてみる。

  • アプリケーションの処理は、hello.rb に記載
  • そのアプリケーションを動作させるための記載は、hello.ru に行う

ということにする。

hello.rb:

require "rubygems"
require "rack"

class HelloApp
  def call(env)
    [200, { 'Content-Type' => 'text/plain' }, ['Hello World!']]
  end
end

hello.ru:

require "hello"
run HelloApp.new

これを動作させるためは、rackup コマンドを使用する。Rack をインストールした際にこのコマンドはインストールされている。Rack アプリケーションを動作させるためのツールで、これで Web サーバを起動する。

上記2つのファイルがあるディレクトリで、

$ rackup hello.ru

をタイプする。そして、やみくもに、ブラウザを起動し、http://localhost:9292/ をリクエストすると、”Hello World!” が表示されていることを確認できる。

rackup には指定可能なオプションが幾つかあり、help で確認できる。

$ rackup -h
Usage: rackup [ruby options] [rack options] [rackup config]

Ruby options:
  -e, --eval LINE          evaluate a LINE of code
  -d, --debug              set debugging flags (set $DEBUG to true)
  -w, --warn               turn warnings on for your script
  -I, --include PATH       specify $LOAD_PATH (may be used more than once)
  -r, --require LIBRARY    require the library, before executing your script

Rack options:
  -s, --server SERVER      serve using SERVER (webrick/mongrel)
  -o, --host HOST          listen on HOST (default: 0.0.0.0)
  -p, --port PORT          use PORT (default: 9292)
  -E, --env ENVIRONMENT    use ENVIRONMENT for defaults (default: development)
  -D, --daemonize          run daemonized in the background
  -P, --pid FILE           file to store PID (default: rack.pid)

Common options:
  -h, --help               Show this message
      --version            Show version

先の Hello World Web アプリケーションだが、環境変数(env)を引数にとる call メソッドで実装されている。

このように、Rack を使用した実装では、call メソッドを定義し、アプリケーションの処理を記述しておけばよい。call メソッドは、リクエスト発生時に、環境変数(env)を引数に呼び出されるので、最後にレスポンスとなるインスタンスを結果として返す処理を書くだけとなる。

また、Proc オブジェクトとしても記載することができる。先の Hello World Web アプリケーションを以下のように書くこともできる。(1ファイルにまとめている)

hello_proc.ru

require "rubygems"
require "rack"

hello_app = Proc.new do |env|
  [200, { 'Content-Type' => 'text/plain' }, ['Hello World!']]
end

run hello_app

サーバの起動は、

$ rackup hello_proc.ru

となる。

Hello World Web アプリケーションだけでは寂しいので、環境変数を表示する ShowEnv アプリケーションも書いてみる。

show_evn.rb:

require "rubygems"
require "rack"

class ShowEnvApp
  def call(env)
    [
      200,
      { 'Content-Type' => 'text/html; charset=UTF-8' },
      [
        env.keys.sort.map do |key|
          Rack::Utils.escape_html("#{key} => #{env[key]}")
        end.join("
\n") ] ] end end

show_env.ru:

require "show_env"

run ShowEnvApp.new

起動する。

$ rackup show_env.ru

以下の内容が画面に表示される。

 GATEWAY_INTERFACE => CGI/1.2
 HTTP_ACCEPT => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 HTTP_ACCEPT_CHARSET => Shift_JIS,utf-8;q=0.7,*;q=0.7
 HTTP_ACCEPT_ENCODING => gzip,deflate
 HTTP_ACCEPT_LANGUAGE => ja,en-us;q=0.7,en;q=0.3
 HTTP_CACHE_CONTROL => max-age=0
 HTTP_CONNECTION => keep-alive
 HTTP_COOKIE => _radiant_080_test_session=abcd
 HTTP_HOST => localhost:9292
 HTTP_KEEP_ALIVE => 300
 HTTP_USER_AGENT => Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ja-JP-mac; rv:1.9.0.11) Gecko/2009060214 Firefox/3.0.11
 HTTP_VERSION => HTTP/1.1
 PATH_INFO => /
 QUERY_STRING =>
 REMOTE_ADDR => 127.0.0.1
 REQUEST_METHOD => GET
 REQUEST_PATH => /
 REQUEST_URI => /
 SCRIPT_NAME =>
 SERVER_NAME => localhost
 SERVER_PORT => 9292
 SERVER_PROTOCOL => HTTP/1.1
 SERVER_SOFTWARE => Mongrel 1.1.5
 rack.errors => #<Rack::Lint::ErrorWrapper:0x114fe00>
 rack.input => #<Rack::Lint::InputWrapper:0x114fe3c>
 rack.multiprocess => false
 rack.multithread => true
 rack.run_once => false
 rack.url_scheme => http
 rack.version => 01

Web サーバには、rackup のデフォルトである webrick/mongrel のうちの Mongrel が使用されていることがわかる。 また、Rack 独自の環境変数も含まれており、rack. という表記になっている。

Rack のミドルウェア

Rack には幾つかのミドルウェアが用意されている。

ここで言うミドルウェアとは、「Response を別の Response に変換する処理」を行ってくれるもののようだ。

以下は、Ruby Rack Middleware Tutorial から。

Your application is called, and then middleware is invoked in the order that you specify. These middlewares call each other, acting as a set of ‘filters’ for the response, so it is important to note that the ordering to which you ‘use’ them can be important, and have an effect on the results.

  • アプリケーションが呼ばれると、ミドルウェアも定義した(use)順番で呼び出される。
  • ミドルウェアは、レスポンスに対するフィルターのように動作する。
  • use を使用してミドルウェアを指定する順番というのは重要。その順番がレスポンスの結果に影響を与える。

とある。上記サイトにあるサンプルソースがその動作を理解するのにわかり易い。

module Rack
  class Upcase
    def initialize app
      @app = app
    end
   
    def call env
      puts 'upcase'
      p @app
      puts
      status, headers, body = @app.call env
      [status, headers, [body.first.upcase]]
    end
  end
end

module Rack
  class Reverse
    def initialize app
      @app = app
    end
   
    def call env
      puts 'reverse'
      p @app
      puts
      status, headers, body = @app.call env
      [status, headers, [body.first.reverse]]
    end
  end
end

use Rack::Upcase
use Rack::Reverse
use Rack::ContentLength

app = lambda { |env| [200, { 'Content-Type' => 'text/html' }, 'Hello World'] }
run app

コンソールには以下の結果が残る。

upcase
#<Rack::Reverse:0x5574e0 @app=#<Rack::ContentLength:0x557620 @app=#<Proc:0x00561210@rack.ru:38>>>

reverse
#<Rack::ContentLength:0x557620 @app=#<Proc:0x00561210@rack.ru:38>>

Rack のその他の特徴

全て見きれていないのだが、URL マッピングなども当然用意されている。

手軽なものからそこそこ凝ったものまで、既に用意されているミドルウェアなどを利用して、Rack を使用した Web アプリケーションを構築する下地はできあがってきているようだ。

Rack のドキュメントは以下にまとまっている。

参考サイト

以下のサイトを参考にさせて頂いた。

blog comments powered by Disqus