僕は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サーバーを立ち上げるためのインターフェースである。