Rack を読む / WEBrick が起動するまで

最近、社内で Rails 関係のコードを読む会を持ち回りでやっていて、今週は僕が担当だったのだけど、Rack をもうちょっとちゃんと知りたかったので、Rack 対応の軽量サーバーである WEBrick を読んだ。pryで実行しながら読んだのだけど、流れを整理しておく。

## TL; DR

具体的には、[公式サイトのトップページ](http://rack.github.io/)に書かれた以下がどのように立ち上がりリクエストを処理するかを追った。

```ruby
require 'rack'
 
app = Proc.new do |env|
    ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]
end
 
Rack::Handler::WEBrick.run app
```

ファイルで言うと以下。

```
<gems directory>/rack-1.6.4/lib/rack/handler/webrick.rb
<ruby lib directory>/webrick/server.rb
<ruby lib directory>/webrick/httpserver.rb
```

最後に、Railsの場合を簡単に見る。

## Rack とは？

前提となる予備知識。Rack については[公式サイト](http://rack.github.io/) のトップページに非常に完結にまとめられていて、Ruby のサーバーとフレームワークのインターフェースであると書いてある。

```
Rack provides a minimal interface between webservers that support Ruby and Ruby frameworks.
```

より具体的に言うと、次のような振る舞いをするオブジェクトは Rack アプリであると言え、Rack 対応のサーバーと連携することができる。

- `call` メソッドに反応すること
- 引数としてハッシュを受け取ること
- 返り値として3要素の配列を返すこと
	- 第1要素：HTTPステータスコード
	- 第2要素：HTTPレスポンスのヘッダーに相当するハッシュ
	- 第3要素：HTTPレスポンスのボディに対応するeachメソッドに反応するオブジェクト

先ほどの例に戻ると、Procオブジェクトはcallで反応するので、ミニマムなRackアプリを作って渡しているということが分かる。

```ruby
require 'rack'

app = Proc.new do |env|
    ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]
end
Rack::Handler::WEBrick.run app
```

## Rack::Handler::Webrick.run(app, options)

rack gem に入っているこのクラスは、この場合は以下を行う。
options には `{BindAddress: 'localhost', Port: 8080}` がデフォルトで入る。

```ruby
@server = ::WEBrick::HTTPServer.new(options)
@server.mount "/", Rack::Handler::WEBrick, app
@server.start
```

これを1行ずつ読んでいく。

### WEBrick::HTTPServer.new(config={}, default=Config::HTTP)

WEBrick::HTTPServer は WEBrick::GenericServer を継承していて、initialize では最初に `super(config, default)` によって上位メソッドを呼び出してる。

#### WEBrick::GenericsServer

重要な部分を抜き出すと以下。

```ruby
@tokens = SizedQueue.new(@config[:MaxClients])
@config[:MaxClients].times{ @tokens.push(nil) }
@listeners = []
listen(@config[:BindAddress], @config[:Port])
```

前半では、最大クライアント数が決まっていて、その大きさで初期化され `nil` が詰まった固定長のキューが`@tokens`として用意されている。SizedQueueはスレッドセーフに実装されたRubyの組み込みクラス。
後半では、最初に設定したアドレスとポートで listen するソケットが`@listeners`に用意されている。

##### WEBrick::HTTPServer
  
こちらの主な仕事はマウントテーブルを作成すること。

```ruby
@mount_tab = MountTable.new
```

サーバーの初期化はこれで終了。Handler::WEBrick.run の次の行を追う。

### HTTPServer#mount(dir, servlet, *options)

```ruby
@mount_tab[dir] = [ servlet, options ]
```

引数を当てはめると、次のようになる。ここでは、Rack::Handler::WEBrickがサーブレットで、Rackアプリに相当するProcはそのオプションとなっている[^1]。

```ruby
@mount_tab['/'] = [ Rack::Handler::WEBrick, [app]]
```

ここまでで準備終了。サーバーを起動する。

### GenericsServer#start(&block)

最後のstartは親クラスのGenericsServerが用意している。今回はブロックは渡していない。configに色々渡せるようになっていたり、起動・終了でコールバックが仕掛けられるようになっていたりするが、骨格は次のようになっている。

```ruby
thgroup = ThreadGroup.new
@status = :Running
while @status == :Running
  if svrs = IO.select(@listeners, nil, nil, 2.0)
    svrs[0].each{|svr|
      @tokens.pop # blocks while no token is there.
        if sock = accept_client(svr)
          th = start_thread(sock, &block)
          thgroup.add(th)
        else
          @tokens.push(nil)
        end
      }
    end
  end
end
```

IO.selectはIO多重化のためのシステムコールをラップする関数。この場合、渡している`@listeners`のいずれかが読む込み可能な状態になるまでブロックし、可能になれば`@listeners`のうち読み込み可能になったもの（配列）を第一要素とする配列svrsを返す。accept_clientによってアクセスのあったクライアントとサーバーが接続されたソケットを返し、それを引数にstart_threadでスレッドが起動する。

#### GenericsServer#start_thread(sock, &block)

```ruby
Thread.start{
  begin
    run(sock)
  ensure
    @tokens.push(nil)
  end
}
```

さっきから出てくる`@tokens`は何に使われているかと言うと、生成するスレッド及び同時に受け付けるクライアントの数を制限している。キューには最初N個の要素が入っていて、クライアントをひとつ受け付けるごとに要素をひとつ減らす。SizedQueue#pop はキューが空の場合、ブロックするようになっているので、N個クライアントを受け付るとブロックする。スレッドが終了時に `@tokens` に対して nil を push することで席を一つ空ける。


### HTTPServer#run(sock)

ここはコールバックを呼んだりアクセスログを残したり、タイムアウトした場合には EOFError をレスポンスに設定して返したりと色々やっているが、それを除くと次のようになっている。

```ruby
while true
  begin
    res = HTTPResponse.new(@config)
    req = HTTPRequest.new(@config)
    server = self
    req.parse(sock)
    server.service(req, res)
  ensure
    res.send_response(sock)
  end
  break unless req.keep_alive?
end
```

リクエストオブジェクトとレスポンスオブジェクトを生成し、リクエストオブジェクトがソケットから読み込む。両方をserviceに渡し、終わった結果をレスポンスオブジェクトがソケットに送信する。ループすることで、複数リクエストを一回のTCPソケットで処理するHTTP1.1の機能 keep_alive を実現している。

### HTTPServer#service(req, res)


```ruby
servlet, options, script_name, path_info = search_servlet(req.path)
si = servlet.get_instance(self, *options)
si.service(req, res)
```

ここでは、リクエストされたパスに対応するサーブレットをマウントテーブルから探し出し、そのインスタンスに対して処理を委譲している。

servletはRack::Handler::WEBrickで、optionsは最初に作ったProcオブジェクトを包む配列になっている。get_instanceはこのservletの場合、newを呼び出しててインスタンスをするだけ。この時、Rackアプリに相当するProcオブジェクトはインスタンス変数 `@app` に割り当てられる。

### Rack::Handler::WEBrick.service(req, res)

RackインターフェースをWEBrickに適合させる部分。3つの部分に分ける。

#### リクエストオブジェクトからenvへの変換

```ruby
env = req.meta_vars
env.delete_if { |k, v| v.nil? }

rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)

env.update({"rack.version" => Rack::VERSION,
             "rack.input" => rack_input,
             "rack.errors" => $stderr,

             "rack.multithread" => true,
             "rack.multiprocess" => false,
             "rack.run_once" => false,

             "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
           })

env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["QUERY_STRING"] ||= ""
unless env["PATH_INFO"] == ""
  path, n = req.request_uri.path, env["SCRIPT_NAME"].length
  env["PATH_INFO"] = path[n, path.length-n]
end
env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env["PATH_INFO"]].join
```

[Rackプロトコル](http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Environment)が要求している値を準備している。大きく、リクエストパスやリクエストメソッドなど処理のために必要な情報と、rackの情報に分かれてる。

#### Rackアプリの呼び出し

```ruby
status, headers, body = @app.call(env)
```

シンプルにRackアプリを呼んでいる。

#### 結果のレスポンスオブジェクトへの変換

```ruby
begin
  res.status = status.to_i
  headers.each { |k, vs|
    if k.downcase == "set-cookie"
      res.cookies.concat vs.split("\n")
    else
      # Since WEBrick won't accept repeated headers,
      # merge the values per RFC 1945 section 4.2.
      res[k] = vs.split("\n").join(", ")
    end
  }
  body.each { |part|
    res.body << part
  }
ensure
  body.close  if body.respond_to? :close
end
```

ステータスコード、ヘッダー、ボディをそれぞれレスポンスオブジェクトに詰めている。heaersのコメントは、[Rackの仕様](http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Headers)としては改行で句切られた多値も受け付けるんだけどWEBrickは対応してないので1行にするというようなことが書かれている。cookieは例外的に対応しているようだ。bodyはeachを使っているので、eachを実装しているオブジェクトであれば例えばファイルでも良いということが分かる。closeするのも仕様なので安心して使える。

## おまけ：結局 Rails ではどうなのか

ここまででまだ rackup コマンドとそこで使えるDSLの動きが見れていないので情報としては足りないのだけど、`$ RACK_HANDLER=webrick rackup config.ru`でRailsアプリケーションがWEBrickで起動できることを合わせて見てみると、Railsの初期化プロセスが分かりやすく書かれていることが分かる。

```ruby
# config.ru
require ::File.expand_path('../config/environment',  __FILE__)
run Huntr::Application # generated by `$ rails new huntr`
```

Handlerとして誰がrunするかをコードに書かなくて良く実行時に選択できるのがこのDSLの一つの利点であることが分かる。

Huntr::Application がどこから来たかと言うと、


```ruby
# config/environment.rb
require File.expand_path('../application', __FILE__)
Huntr::Application.initialize!
```

最初に environment.rb を読み込んで、

```ruby
# config/application.rb
require File.expand_path('../boot', __FILE__)

require 'rails/all'

Bundler.require(*Rails.groups)

module Huntr
  class Application < Rails::Application
    …
  end
end
```

application.rb がgemを読み込んだ上で `Huntr::Application` を用意していることが分かる。

## その他参考になる解説

Rack Middleware や rackup コマンドなども含めた話は次の記事が要点を得ていて分かりやすい。

[Rackとは何か - Qiita](http://qiita.com/k0kubun/items/248395f68164b52aec4a)

また、なぜつくったかは作者である Christian Neukirchen の次の記事に短くまとまっている。

[chris blogs: Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html)

[^1]: ここにHandlerが入るのは最初不思議に思ったが、次のように解釈した。サーブレットはリクエストに対して起動される処理のことを意味する。Rack::Handler::WEBrickはrun(app, options)を実装しているRackハンドラであり、後で見るようにそのインターフェースに合わせるための変換処理を行うが、WEBrickサーバーの立場からはその処理も含めてサーブレットになる。