僕はRackミドルウェアを何個か書いたことがあるけど、この前Rubyを始めたばかりの人に「Rackって何?」って聞かれた時、ちゃんと答えられなかった。
なので、rack/rackは何を実装していて、RailsやSinatraはどのようにRackを利用しているのかについてRack 1.6.4のコードを読みつつ調べてみた。
Rackとは何か
Rackは、指定したファイルを独自のRuby DSLとして読み込み、DSLで指定した様々なミドルウェア、アプリケーションを組み合わせてWebサーバを立ち上げることができるrackupというコマンドを提供するライブラリである。
Rack::Serverの仕組み
rackupコマンドはRack::Server.startしているだけであり、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に実装されていて、以下の4つを含む。
| DSL | 意味 |
|---|---|
| run | 後述のRackアプリケーションを指定する。これがベースの挙動になる。runだけは指定が必須。 |
| use | 後述のRackミドルウェアを指定する。 |
| warmup | Procオブジェクトを渡すとサーバが立ち上がる前に評価される。 |
| map | リクエストパスに応じてミドルウェアやアプリケーションを切り替えるために使う。 |
Rackアプリケーション
Rackアプリケーションは、引数を1つ取り3つの値を返すcallを呼び出すことができるオブジェクトであると定義されており、Rack protocolと呼ばれる仕様を満たすことが期待されている。
RailsやSinatraで書くアプリケーションがこれに属する。
Rack protocolに定義されているcallの引数や返り値の具体的な内容は以下の通りである。
| 種別 | 名前 | 内容 |
| 引数 | environment | HTTPリクエストヘッダなどを含むHash。 |
| 返り値 | status |
to_iした結果が100以上になるオブジェクト。HTTPステータスコードになる。 |
| headers |
eachを呼ぶとキーと値をyieldするオブジェクト。HTTPレスポンスヘッダに使われる。 |
|
| body |
eachを呼ぶとStringをyieldするオブジェクト。HTTPレスポンスボディに使われる。 |
Rackミドルウェア
Rackアプリケーションのcallの前後に行いたい処理を記述するクラス。Rackアプリケーションを引数にinitializeすると、そのアプリケーションをcallするRackアプリケーションを返すことが期待される。
rack-mini-profilerなど、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では、環境変数RACK_HANDLERが定義されていればそれを使い、そうでなければThin > Puma > WEBrickの優先順位で定義されているものを使用する。
Rails::Serverの仕組み
railsの開発でrackupを叩く人はほとんどいないと思う。通常叩くrails sはRack::ServerのサブクラスであるRails::Serverをstartしている。
Rails::Serverでは、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!がフックされる。
Rails::Serverと違ってSinatra::ApplicationはRack::Serverのサブクラスではないので、newの際にRack::Builder DSLを呼んだり、run!の際にRack::Handlerを探したりする実装が含まれている。
まとめ
RackはRubyでWebサーバーを立ち上げるためのインターフェースである。