Rackとは何か

僕はRackミドルウェアを何個か書いたことがあるけど、この前Rubyを始めたばかりの人に「Rackって何？」って聞かれた時、ちゃんと答えられなかった。
なので、[rack/rack](https://github.com/rack/rack)は何を実装していて、RailsやSinatraはどのようにRackを利用しているのかについてRack 1.6.4のコードを読みつつ調べてみた。

# Rackとは何か
[Rack](https://rubygems.org/gems/rack)は、指定したファイルを独自のRuby DSLとして読み込み、DSLで指定した様々なミドルウェア、アプリケーションを組み合わせてWebサーバを立ち上げることができる`rackup`というコマンドを提供するライブラリである。

# Rack::Serverの仕組み
`rackup`コマンドは[`Rack::Server.start`しているだけ](https://github.com/rack/rack/blob/1.6.4/bin/rackup#L4)であり、`rackup`によって立ち上がるWebサーバの挙動を理解するには`Rack::Server`の仕組みを知る必要がある。

`Rack::Server.start`は以下の手順でWebサーバを立ち上げる。

- config.ruをRack::Builder DSLとして評価し、Rackアプリケーションを生成
- RACK_ENVに応じてRackアプリケーションにRackミドルウェアをラップする
- RackミドルウェアをラップしたRackアプリケーションをRackハンドラに渡す

以下で各用語の意味を説明する。

## Rack::Builder DSL
RackはRack::Builder DSLという独自のDSLを提供している。`rackup [rackup config]`を叩くと`[rackup config]`に指定したファイルをRack::Builder DSLとして読み込み、そのDSLによって設定されたWebサーバーが立ち上がる。rackup configの指定を省略するとカレントディレクトリのconfig.ruが読み込まれる。

Rack::Builder DSLは[rack/builder.rb#L62-L142](https://github.com/rack/rack/blob/1.6.4/lib/rack/builder.rb#L62-L142)に実装されていて、以下の4つを含む。

| DSL | 意味 |
|:----|:----|
| run | 後述のRackアプリケーションを指定する。これがベースの挙動になる。runだけは指定が必須。 |
| use | 後述のRackミドルウェアを指定する。 |
| warmup | Procオブジェクトを渡すとサーバが立ち上がる前に評価される。 |
| map    | リクエストパスに応じてミドルウェアやアプリケーションを切り替えるために使う。 |

## Rackアプリケーション
Rackアプリケーションは、引数を1つ取り3つの値を返す`call`を呼び出すことができるオブジェクトであると[定義されており](https://github.com/rack/rack/blob/1.6.4/SPEC#L6-L12)、[Rack protocolと呼ばれる仕様](http://www.rubydoc.info/github/rack/rack/master/file/SPEC)を満たすことが期待されている。
RailsやSinatraで書くアプリケーションがこれに属する。

Rack protocolに定義されている`call`の引数や返り値の具体的な内容は以下の通りである。

<table>
<thead><tr><td>種別</td><td>名前</td><td>内容</td></tr></thead>
<tbody>
<tr><td>引数</td><td>environment</td><td>HTTPリクエストヘッダなどを含む<code>Hash</code>。</td></tr>
<tr><td rowspan='3'>返り値</td><td>status</td><td><code>to_i</code>した結果が100以上になるオブジェクト。HTTPステータスコードになる。</td></tr>
<tr><td>headers</td><td><code>each</code>を呼ぶとキーと値を<code>yield</code>するオブジェクト。HTTPレスポンスヘッダに使われる。</td></tr>
<tr><td>body</td><td><code>each</code>を呼ぶと<code>String</code>を<code>yield</code>するオブジェクト。HTTPレスポンスボディに使われる。</td></tr>
</tbody>
</table>

## Rackミドルウェア
Rackアプリケーションの`call`の前後に行いたい処理を記述するクラス。Rackアプリケーションを引数に`initialize`すると、そのアプリケーションを`call`するRackアプリケーションを返すことが期待される。
rack-mini-profilerなど、[rack-](https://rubygems.org/search?utf8=%E2%9C%93&query=rack-)のつくgemは大体Rackミドルウェアとして実装されている。

Rack::Builder DSL内で複数のRackミドルウェアを`use`することでミドルウェアスタックが作られる。`use`した順にリクエストが処理されてベースのRackアプリケーションに渡され、そのレスポンスを`use`の逆順に処理されることになる。

## Rackハンドラ
RackミドルウェアにラップされたRackアプリケーションと、portやhostなどのオプションを受け取って実際にWebサーバを立ち上げるクラス。HTTPリクエストをパースしたりHTTPレスポンスを組み立てるのはRackハンドラの役割である。
WEBrick, Thin, Pumaなどがこれに属する。

[Rack::Handler.default](https://github.com/rack/rack/blob/1.6.4/lib/rack/handler.rb#L46-L61)では、環境変数`RACK_HANDLER`が定義されていればそれを使い、そうでなければThin > Puma > WEBrickの優先順位で定義されているものを使用する。

# Rails::Serverの仕組み
railsの開発で`rackup`を叩く人はほとんどいないと思う。通常叩く`rails s`は`Rack::Server`のサブクラスである[`Rails::Server`を`start`している](https://github.com/rails/rails/blob/4-2-stable/railties/lib/rails/commands/commands_tasks.rb#L71-L82)。

[Rails::Server](https://github.com/rails/rails/blob/4-2-stable/railties/lib/rails/commands/server.rb)では、opt_parserをオーバーライドして`rails s`特有のオプションを定義したり、デフォルトポートを3000にしたり、RAILS_ENVに応じた処理が定義されている。
Railsアプリにもconfig.ruはおいてあるが、`rackup`を叩いてしまうと`Rails::Server`ではなく`Rack::Server`がstartされるので、これらの恩恵を受けることができなくなる。

# Sinatra::Applicationの仕組み
`require "sinatra"`すると`at_exit`で`Sinatra::Application.run!`が[フックされる](https://github.com/sinatra/sinatra/blob/v1.4.6/lib/sinatra/main.rb#L25)。

`Rails::Server`と違って`Sinatra::Application`は`Rack::Server`のサブクラスではないので、`new`の際に[Rack::Builder DSLを呼んだり](https://github.com/sinatra/sinatra/blob/v1.4.6/lib/sinatra/base.rb#L1477-L1483)、`run!`の際に[`Rack::Handler`を探したり](https://github.com/sinatra/sinatra/blob/v1.4.6/lib/sinatra/base.rb#L1777)する実装が含まれている。

# まとめ
RackはRubyでWebサーバーを立ち上げるためのインターフェースである。