たのしいOSSコードリーディング: Let’s read WEBrick🏠

[Ruby Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ruby) 9日目の記事です。昨日は[mtsmfm](https://qiita.com/mtsmfm)さんの[ensure は実行されるとは限らない](https://qiita.com/mtsmfm/items/c967e0624bbafb25f2a3)でした。

## Let’s read WEBrick🏠
この記事では、Ruby製HTTPサーバーフレームワークである[WEBrick](https://ja.wikipedia.org/wiki/WEBrick)のソースコードリーディングを行います。

#### 経緯
去る2020年11月26日、地域Rubyコミュニティ[Tama.rb](https://tamarb.connpass.com/)の公開イベントとして[Tama.rb OSSコードリーディング部 WEBrick編](https://tamarb.connpass.com/event/194504/)を開催しました。
こちらは、原作者の一人である高橋征義さん @takahashim をお招きしてソースコードをわいわい読み、その様子をZoomでライブ配信するという試みでした。

当日学んだWEBrick内部の全体の処理の流れを振り返りつつ整理していきたいと思います。
長い記事ですが、どうぞお付き合いください🙏

## WEBrickとは？
- 引用: [library webrick](https://docs.ruby-lang.org/ja/2.7.0/library/webrick.html)

> 汎用HTTPサーバーフレームワークです。HTTPサーバが簡単に作れます。

現在はCRuby本体に同梱のHTTPサーバーツールキットとして知られているWEBrickですが、高橋さんにお話を伺ったところ、元々は2000年頃雑誌の連載のために開発されたプログラムだったそう。
現在のWEBrickの骨格は開発当時とほとんど変わっていないそうで、開発当初から綺麗な設計を目指していたことが伺えます。

> WEBrick はサーブレットによって機能します。サーブレットとはサーバの機能をオブジェクト化したものです。ファイルを読み込んで返す・forkしてスクリプトを実行する・テンプレートを適用するなど、「サーバが行なっている様々なこと」を抽象化しオブジェクトにしたものがサーブレットです。サーブレットは WEBrick::HTTPServlet::AbstractServlet のサブクラスのインスタンスとして実装されます。

WEBrickの一番の特徴は、何と言ってもサーブレット(サーバー機能)と組み合わせることで簡単にHTTPサーバーを構築できてしまうという点でしょう。

以下は[るりま](https://docs.ruby-lang.org/ja/)に掲載されているサンプルコード（抜粋）です。
わずかな行数ですが、サーバープロセスとCGIを扱うためのサーブレットを結びつけてHTTPサーバーを構築し、起動しています。

```ruby
require 'webrick'
srv = WEBrick::HTTPServer.new({ :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080 })
srv.mount('/view.cgi', WEBrick::HTTPServlet::CGIHandler, 'view.rb')
srv.start
```

> 1. サーバのパス /view.cgi と CGIHandler がマウントにより結びつけられます。
> 2. パス /view.cgi にアクセスがあるたびにサーバは 'view.rb' を引数として CGIHandler オブジェクトを生成します。
> 3. サーバはリクエストオブジェクトを引数として CGIHandler#service メソッドを呼びます。
> 4. CGIHandler オブジェクトは view.rb を CGI スクリプトとして実行します。


このコード例では、

**① サーバーインスタンスの生成**
（`WEBrick::HTTPServer`クラスのインスタンスをつくる）

**② サーブレットのマウント**
（`WEBrick::HTTPServer`クラスのインスタンスにおいて、パス`'/view.cgi'`と`WEBrick::HTTPServlet::CGIHandler`クラスを結びつける）

**③ サーバーの起動**
（`WEBrick::HTTPServer`クラスのインスタンスに対して`WEBrick::HTTPServer#start`を呼ぶ）

**④ パスに対してリクエストが発生**
（クライアントが`'/view.cgi'`にアクセスする）

**⑤ サーブレットが処理を実行**
（`WEBrick::HTTPServlet::CGIHandler`クラスがインスタンスを作り、`WEBrick::HTTPServlet::CGIHandler#service`メソッドを呼ぶ）

という流れでHTTPサーバーを構築し、実際に処理を行っています。
 
ちなみに、サーブレットはWEBrickに組み込まれているもの（Ex.[WEBrick::HTTPServlet::CGIHandler](https://docs.ruby-lang.org/ja/2.7.0/class/WEBrick=3a=3aHTTPServlet=3a=3aCGIHandler.html)）だけでなく、抽象サーブレットクラスである[WEBrick::HTTPServlet::AbstractServlet](https://docs.ruby-lang.org/ja/2.7.0/class/WEBrick=3a=3aHTTPServlet=3a=3aAbstractServlet.html)を継承することによってオリジナルのサーブレットを記述することができるようになっています。便利！

それでは、このサンプルコードの流れに沿ってWEBrickのコードを読んでいきましょう！

## 対象
この記事では、 ruby/webrickリポジトリ https://github.com/ruby/webrick より、
2020年12月2日時点でのmasterブランチを参照しました。

#### ❗️注意❗️
今記事では、上記のサンプルコードの流れに沿ってコードリーディングを進めていきますが、都合上必要なソースコードの該当箇所のみを抜粋してご紹介します。
場合により元のコードを大幅に割愛した上掲載している箇所がありますので、予めご了承ください。

## サンプルコード

- 出典: https://docs.ruby-lang.org/ja/

```ruby
require 'webrick'
srv = WEBrick::HTTPServer.new({ :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080 })
srv.mount('/view.cgi', WEBrick::HTTPServlet::CGIHandler, 'view.rb')
srv.start
```

## ① サーバーインスタンスの生成
まずはサンプルコードの一行目をご覧ください。

```ruby
srv = WEBrick::HTTPServer.new({ :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080 })
```

ここでは`WEBrick::HTTPServer`クラスのインスタンスを生成しています。
`WEBrick::HTTPServer#initialize`を読んでみましょう。

- 抜粋: [webrick/lib/webrick/httpserver.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/httpserver.rb)

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    def initialize(config={}, default=Config::HTTP)
      super(config, default)
      # ...
```


いきなりですが`WEBrick::HTTPServer`クラスは`WEBrick::GenericServer`クラスを継承しており、`super`でスーパークラスの`#initialize`を呼んでいます。

またこのとき、
引数`config`には`{ :DocumentRoot => './', :BindAddress => '127.0.0.1', :Port => 20080 }`
引数`default`には`Config::HTTP`が渡されています。

先に`Config::HTTP`を確認しておきましょう。

- 抜粋： [webrick/lib/webrick/config.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/config.rb)

```ruby
module WEBrick
  module Config
    # ...
    HTTP = General.dup.update(
      :Port           => 80,
      :RequestTimeout => 30,
      :HTTPVersion    => HTTPVersion.new("1.1"),
      :AccessLog      => nil,
      :MimeTypes      => HTTPUtils::DefaultMimeTypes,
      :DirectoryIndex => ["index.html","index.htm","index.cgi","index.rhtml"],
      :DocumentRoot   => nil,
   　　# ...
    )
    # ...
```

ここで`#dup`のレシーバーになっている`WEBrick::Config::General`も見てみましょう。

```ruby
    General = Hash.new { |hash, key|
      case key
      when :ServerName
        hash[key] = Utils.getservername
      else
        nil
      end
    }.update(
      :BindAddress    => nil,   # "0.0.0.0" or "::" or nil
      :Port           => nil,   # users MUST specify this!!
      :MaxClients     => 100,   # maximum number of the concurrent connections
      :ServerType     => nil,   # default: WEBrick::SimpleServer
      :Logger         => nil,   # default: WEBrick::Log.new
      :ServerSoftware => "WEBrick/#{WEBrick::VERSION} " +
                         "(Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})",
      :TempDir        => ENV['TMPDIR']||ENV['TMP']||ENV['TEMP']||'/tmp',
      :DoNotListen    => false,
      :StartCallback  => nil,
      :StopCallback   => nil,
      :AcceptCallback => nil,
      :DoNotReverseLookup => true,
      :ShutdownSocketWithoutClose => false,
    )
```

どうやら、サーバーの動作設定のために必要な情報が格納されたハッシュのようです。

続けて、これらの引数に取る`WEBrick::GenericServer#initialize`のコードを確認します。

- 抜粋: [weblick/lib/webrick/server.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/server.rb)

```ruby
class WEBrick
  # ...
  class GenericServer
    def initialize(config={}, default=Config::General)
      @config = default.dup.update(config)
      # ...
      @listeners = []
      # ...
      unless @config[:DoNotListen]
        # ...
        listen(@config[:BindAddress], @config[:Port])
        # ...
      end
    end
    # ...
```


引数`default`に`WEBrick::Config::General`が入っていますが、ここはサブクラスの`WEBrick::HTTPServer#initialize`で`WEBrick::Config::HTTP`によって上書きされています。

それを踏まえると、インスタンス変数`@config`には`WEBrick::Config::HTTP`を`dup`し、`{ :DocumentRoot => './', :BindAddress => '127.0.0.1', :Port => 20080 }`で上書きしたハッシュが代入されていることになります。

また、インスタンス変数`@listeners`には空配列が代入されています。

ここではそれ以外にも色々な設定事項が記述されていますが、一番大事なのは最後の`listen(@config[:BindAddress], @config[:Port])`です。

`#listen`は同じ`WEBrick::GenericServer`クラスに実装されています。

```ruby
    def listen(address, port)
      @listeners += Utils::create_listeners(address, port)
    end
```
インスタンス変数`@listeners`は空配列でした。
ここに`WEBrick::Utils.create_listeners`の返り値の配列を代入しています。
`WEBrick::Utils.create_listeners`の実装は次の通りです。

- 抜粋: [webrick/lib/webrick/utils.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/utils.rb)

```ruby
module WEBrick
  module Utils
    # ...
    def create_listeners(address, port)
      # ...
      sockets = Socket.tcp_server_sockets(address, port)
      sockets = sockets.map {|s|
        s.autoclose = false
        ts = TCPServer.for_fd(s.fileno)
        s.close
        ts
      }
      return sockets
    end
    # ...
```
ここで、面白いことをしています。

`sockets = Socket.tcp_server_sockets(address, port)`という行において、`Socket.tcp_server_sockets`の返り値は、IPv4とIPv6のソケットを一つずつ格納した配列です。

この配列の要素である二つのソケットは、いずれも汎用的なソケットの概念を扱う`Socket`クラスのインスタンスとなっています。

- 参照: [singleton method Socket.tcp_server_sockets](https://docs.ruby-lang.org/ja/2.7.0/method/Socket/s/tcp_server_sockets.html)
- 参照: [class Socket](https://docs.ruby-lang.org/ja/2.7.0/class/Socket.html)

しかしRubyは汎用的なソケットの概念を扱う`Socket`クラス以外にも、TCP/IPストリーム型接続のサーバーに特化したソケットの概念を扱う`TCPServer`クラスを持っています。
いずれも同じソケットを扱うクラスではありますが、WEBrickのようなHTTPサーバーの概念を扱う場合には、後者の方が都合が良いのです。

- 参照: [class TCPServer](https://docs.ruby-lang.org/ja/2.7.0/class/TCPServer.html)

そのため、ここでは`Socket.tcp_server_sockets`の返り値である`Socket`クラスのインスタンスの配列を`map`で回し、`Socket`クラスのオブジェクトを元に`TCPServer`クラスのオブジェクトを生成しています。すごい。

- 参照: [singleton method BasicSocket.for_fd](https://docs.ruby-lang.org/ja/2.7.0/method/BasicSocket/s/for_fd.html)

ということで、`WEBrick::Utils.create_listeners`の返り値は、それぞれIPv４とIPv6に対応する二つの`TCPServer`クラスのオブジェクトを要素に持つ配列、ということになります。

`WEBrick::GenericServer#listen`に戻りましょう。

```ruby
    def listen(address, port)
      @listeners += Utils::create_listeners(address, port)
    end
```

インスタンス変数`@listeners`には次のような配列が格納されています。

```ruby
[IPv4に対応したTCPServerクラスのインスタンス, IPv6に対応したTCPServerクラスのインスタンス]
```
 
それでは`WEBrick::GenericServer#initialize`を読み終えたので、最初に戻って`WEBrick：：HTTPServer#initialize`を読んでみます。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    def initialize(config={}, default=Config::HTTP)
      super(config, default)
      # ...
      @mount_tab = MountTable.new
      # ...
    end
```

`@mount_tab = MountTable.new`という行が登場しました。
同じファイルに記述されている`WEBrick::HTTPServer：：Mountable#initialize`を読んでみます。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      def initialize
        @tab = Hash.new
        compile
      end
```

`@tab`には空ハッシュが代入されています。
`compile`が気になりますね。
`WEBrick::HTTPServer：：MountTable#compile`を読んでみましょう。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      # ...
      def compile
        k = @tab.keys
        k.sort!
        k.reverse!
        k.collect!{|path| Regexp.escape(path) }
        @scanner = Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)")
      end
```

先ほどの`WEBrick::HTTPServer：：Mountable#initialize`にて、`@tab`は空ハッシュでした。
`@tab.keys`の返り値は空配列なので、一旦ここでは`@scanner`に`/\A()(?=\/|\z)/`が入ることになります。
この`WEBrick::HTTPServer：：MountTable#compile`はすぐ後にまた登場します。

ここまでが **① サーバーインスタンスの生成** のコードになります。

### まとめ
ここまでで行われた処理は以下の通りです。

- `WEBrick::HTTPServer`クラスのインスタンスを生成
- `WEBrick::HTTPServer`クラスのインスタンスはインスタンス変数`@config`、`@listeners`、`@mount_tab`を持つ
  - `@config` ➡️ 様々なサーバーの設定項目
  - `@listeners` ➡️ それぞれIPv4/IPv６対応の二つの`TCPServer`インスタンスからなる配列
  - `@mount_tab` ➡️ `WEBrick::HTTPServer::Mountable`クラスのインスタンス
      - `@mount_tab`はインスタンス変数`@tab`と`@scanner`を持つ
          - `@tab` ➡️ `{}`
          - `@scanner` ➡️ `/\A()(?=\/|\z)/`

## ② サーブレットのマウント
続いて、サンプルコードよりこの行の実装を読んでみます。
（ちなみに、変数`srv`は`WEBrick::HTTPServer`クラスのインスタンスでしたね）

```ruby
srv.mount('/view.cgi', WEBrick::HTTPServlet::CGIHandler, 'view.rb')
```

`#mount`メソッドは`WEBrick::HTTPServer`クラスに実装されています。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def mount(dir, servlet, *options)
      # ...
      @mount_tab[dir] = [ servlet, options ]
    end
```

引数`dir`には`'/view.cgi'`、
引数`servlet`には`WEBrick::HTTPServlet::CGIHandler`、
引数`options`には`'view.rb'`が渡されています。

インスタンス変数`@mount_tab`には`WEBrick::HTTPServer：：MountTable`クラスのインスタンスが入っているのでした。
これに対して、`#[]=`を呼び出しています。

`WEBrick::HTTPServer：：MountTable#[]=`を見てみましょう。


```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      # ...
      def []=(dir, val)
        dir = normalize(dir)
        @tab[dir] = val
        compile
        val
      end

      # ...

      def normalize(dir)
        ret = dir ? dir.dup : ""
        ret.sub!(%r|/+\z|, "")
        ret
      end
```

引数`dir`には`'/view.cgi'`、
引数`val`には`[WEBrick::HTTPServlet::CGIHandler, 'view.rb']`が渡されています。

まず`dir = normalize(dir)`で、すぐ下にある`WEBrick::HTTPServer：：MountTable#normalize`メソッドを呼び出しています。
ここで、引数`dir`に格納されている`'/view.cgi'`に対して、末尾に`/`がある場合は空文字`''`に置き換えます（なので今回は対象外）

その後`@tab[dir] = val`で、インスタンス変数`@tab`（この時点では空ハッシュ）に対し、`'/view.cgi'`をキーとして値`[WEBrick::HTTPServlet::CGIHandler, 'view.rb']`を追加しています。

結果、`@tab`はこのようなハッシュになります。

```ruby
{ '/view.cgi' => [WEBrick::HTTPServlet::CGIHandler, 'view.rb'] }
```

次に、再び`WEBrick::HTTPServer：：MountTable#compile`を呼んでいます。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      # ...
      def compile
        k = @tab.keys
        k.sort!
        k.reverse!
        k.collect!{|path| Regexp.escape(path) }
        @scanner = Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)")
      end
```

この時、`k = @tab.keys`で変数`k`に`['/view.cgi']`が代入されます。
配列の要素が一つしかないので次の二行は飛ばします。
`k.collect!{|path| Regexp.escape(path) }`が呼ばれると、変数`'view.cgi'`は次のような配列に変わります。

```ruby
["view\\.cgi"]
```

- 参照: [Regexp.escape](https://docs.ruby-lang.org/ja/2.7.0/method/Regexp/s/escape.html)

最後の行`Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)")`を呼ぶと、インスタンス変数`@scanner`には次のようなRegexpオブジェクトが格納されます。

```ruby
/\A(\/view\.cgi)(?=\/|\z)/
```

`WEBrick::HTTPServer：：MountTable#[]=`に戻ります。
最後の行で`val`(= `[WEBrick::HTTPServlet::CGIHandler, 'view.rb']`)を返してこのメソッドは終了です。

```ruby

module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      # ...
      def []=(dir, val)
        dir = normalize(dir)
        @tab[dir] = val
        compile
        val
      end
```

ここまでが **② サーブレットのマウント** のコードです。

### まとめ
ここまでで行われた処理は以下の通りです。

- `@mount_tab`(= `WEBrick::HTTPServer：：MountTable`クラスのインスタンス)の持つインスタンス変数が次のように変化
  - `@tab` ➡️ `{ '/view.cgi' => [WEBrick::HTTPServlet::CGIHandler, 'view.rb'] }`
  - `@scanner` ➡️ `/\A(\/view\.cgi)(?=\/|\z)/`

## ③ サーバーの起動
続いて読むのは、サンプルコードの最後の一行の部分です。

```ruby
srv.start
```

`srv`、つまり`WEBrick::HTTPServer`クラスのインスタンスに`#start`メソッドを呼んでいます。
この一行を実行すると、HTTPサーバーが起動することになります。

早速コードを読んでみましょう。と言いたいところですが、実は`#start`メソッドは`WEBrick::HTTPServer`クラスには実装されていません。
`WEBrick::HTTPServer`クラスのスーパークラスである`WEBrick::GenericServer`クラスに実装されています。

`WEBrick::GenericServer#start`は非常に長いメソッドなので、先に全容を掲載しておきます。
その後、今回コードリーディングを行う箇所に限定して少しずつ読んでいきます。

```ruby
class WEBrick
  # ...
  class GenericServer
    # ...
    def start(&block)
      raise ServerError, "already started." if @status != :Stop
      server_type = @config[:ServerType] || SimpleServer

      setup_shutdown_pipe

      server_type.start{
        @logger.info \
          "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
        @status = :Running
        call_callback(:StartCallback)

        shutdown_pipe = @shutdown_pipe

        thgroup = ThreadGroup.new
        begin
          while @status == :Running
            begin
              sp = shutdown_pipe[0]
              if svrs = IO.select([sp, *@listeners])
                if svrs[0].include? sp
                  # swallow shutdown pipe
                  buf = String.new
                  nil while String ===
                            sp.read_nonblock([sp.nread, 8].max, buf, exception: false)
                  break
                end
                svrs[0].each{|svr|
                  @tokens.pop          # blocks while no token is there.
                  if sock = accept_client(svr)
                    unless config[:DoNotReverseLookup].nil?
                      sock.do_not_reverse_lookup = !!config[:DoNotReverseLookup]
                    end
                    th = start_thread(sock, &block)
                    th[:WEBrickThread] = true
                    thgroup.add(th)
                  else
                    @tokens.push(nil)
                  end
                }
              end
            rescue Errno::EBADF, Errno::ENOTSOCK, IOError => ex
              # if the listening socket was closed in GenericServer#shutdown,
              # IO::select raise it.
            rescue StandardError => ex
              msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
              @logger.error msg
            rescue Exception => ex
              @logger.fatal ex
              raise
            end
          end
        ensure
          cleanup_shutdown_pipe(shutdown_pipe)
          cleanup_listener
          @status = :Shutdown
          @logger.info "going to shutdown ..."
          thgroup.list.each{|th| th.join if th[:WEBrickThread] }
          call_callback(:StopCallback)
          @logger.info "#{self.class}#start done."
          @status = :Stop
        end
      }
    end
```

それでは、具体的な内容を少しずつ読んでいきます。

```ruby
class WEBrick
  # ...
  class GenericServer
    # ...
    def start(&block)
      # ...
      server_type = @config[:ServerType] || SimpleServer
```

今回引数にブロックは渡していないので無視します。
`@config[:ServerType]`は`nil`なので、変数`server_type`には`WEBrick::SimpleServer`クラスが代入されます。

`WEBrick::SimpleServer`クラスは`WEBrick::GenericServer`クラスと[同じファイル](https://github.com/ruby/webrick/blob/master/lib/webrick/server.rb)に記述されています。

```ruby
module WEBrick
  # ...
  class SimpleServer

    ##
    # A SimpleServer only yields when you start it

    def SimpleServer.start
      yield
    end
  end
```

シンプル！
コメントに記載の通り、`WEBrick::SimpleServer`クラスには、渡されたブロックを実行するだけのクラスメソッド`.start`が実装されているのみです。

`WEBrick::GenericServer#start`メソッドに戻ると、この後は次のような記述が続きます。

```ruby
    def start(&block)
      # ...
      server_type = @config[:ServerType] || SimpleServer
      # ...
      server_type.start{
        # ...
      }
    end
```

`WEBrick::GenericServer#start`メソッドは、先ほどの`WEBrick::SimpleServer`クラスに対して`.start`メソッドを呼び、ブロックの中の処理を実行しているだけのメソッドであるということがわかります。
ブロックの中の処理を見てみましょう。

```ruby
      server_type.start{
        # ...
        @status = :Running
        # ...
        begin
          while @status == :Running
            begin
              # ...
              if svrs = IO.select([sp, *@listeners])
                # ...
```

インスタンス変数`@status`に`:Running`を代入し、それを条件として、「クライアントからのリクエストを待つ→リクエストを処理する」というループに入ります。

下から二番目の行の条件分岐で`sp`という変数が唐突に登場しています。都合上今回は紹介しませんが、シャットダウンの仕組みに関わるとても重要で面白い概念なので、興味がある方はぜひ読んでみてください。
(`@shutdown_pipe`、`WEBrick::GenericServer#setup_shutdown_pipe`辺りです）

さて条件文を見ると、`IO.select`の返り値を確認しています。

- 参照: [singleton method IO.select](https://docs.ruby-lang.org/ja/2.7.0/method/IO/s/select.html)

`IO.select`の詳しい解説はるりまに譲りますが、今回、この`IO.select`の引数には`[sp, *@listeners]`という配列が渡されています。
この配列に格納されている各要素は、いずれも「クライアントからの接続要求待ち」のソケットオブジェクトです。

`@listeners`の中身が何だったか覚えていますか？
そう、IPv4とIPv6のそれぞれソケットオブジェクト（`TCPServer`クラスのインスタンス）からなる配列でした。

この場合splatされているので、実際引数に渡されているのは次のような配列です。

```ruby
[sp, IPv4のソケットオブジェクト, IPv6のソケットオブジェクト]
```

この配列の各要素のうち、いずれかのソケットがクライアントからの接続要求を感知した時、`IO.select`は「①当該ソケットオブジェクトを要素に持つ配列②空配列(1)③空配列(2)」という三つの要素を持つ二次元配列を返します。
つまり、上記の変数`svrs`には次のような配列が代入されます。

```ruby
[[接続要求を感知したソケットオブジェクト], [], []]
```

なぜこのような形の返り値になるのか、については（面白いのですが）割愛します。
上記のるりまの`IO.select`の項やUnixのシステムコール`select(2)`のマニュアルの他、書籍「Working with TCP Sockets Chapter12」に詳しく解説されていますので、ご興味のある方にはお勧めです。
なお、今回のケースでは後ろの二つの空配列を気にする必要はありません。

- 参照: [select(2)](https://pubs.opengroup.org/onlinepubs/009695399/functions/select.html)
- 参照: [Working with TCP Sockets by Jesse Storimer](https://www.jstorimer.com/products/working-with-tcp-sockets)
  - ちなみにこの本は[達人出版会](https://tatsu-zine.com/)から出版されている「[なるほどUnixプロセス](https://tatsu-zine.com/books/naruhounix)」の続編として位置付けられています

さて、ここで改めて先ほどの条件文を見てみましょう。

```ruby
              if svrs = IO.select([sp, *@listeners])
```

`IO.select`はブロッキング処理です。
つまり、`IO.select`が「接続要求を感知したソケットオブジェクト」を返すまで、ここで一旦処理が止まることになります。

では、この条件文はいつ`true`になるのでしょう？そうです、クライアントからのリクエストが到着したときです。

なので、ちょっと中途半端ですが **③ サーバーの起動** は一旦ここまでにして、この先のコードは次の処理 **④ パスに対してリクエストが発生** の中で読んでいくことにしましょう。

### まとめ
ここまでで行われた処理は以下の通りです。

- `SimpleServer.start`の実行
  -  「クライアントからのリクエストを待つ→リクエストを処理する」というループに入る
  - ソケットオブジェクトがクライアントからのリクエストを待つ

## ④ パスに対してリクエストが発生
次の条件文にて、`IO.select`が引数に渡したソケットオブジェクトのうち、いずれかがクライアントからの接続要求を感知するまでは処理が止まる、と説明しました。

```ruby
              if svrs = IO.select([sp, *@listeners])
```

これに対して、るりまのサンプルコードには次のような解説がついてます。

> 1. サーバのパス /view.cgi と CGIHandler がマウントにより結びつけられます。
> 2. パス /view.cgi にアクセスがあるたびにサーバは 'view.rb' を引数として CGIHandler オブジェクトを生成します。

ここで注目すべきは`2`の方です。
ここから先の処理を読むにあたり、記載の通り「クライアントからパス`/view.cgi`にアクセスがあり、その結果ソケットオブジェクトが接続要求を感知した」と仮定しましょう。

それでは、条件分岐の先に進みます。


```ruby 
              if svrs = IO.select([sp, *@listeners])
                svrs[0].each{|svr|
                  # ...
                  if sock = accept_client(svr)
                    # ...
                    th = start_thread(sock, &block)
                    # ...
                  else
                    # ...
                  end
                }
                # この後はエラー処理と終了処理が続く
```

`svrs`は`[[接続要求を感知したソケットオブジェクト], [], []]`という二次元配列です。
なので、`svrs[0]`は`[接続要求を感知したソケットオブジェクト]`という配列です。

この配列に対して`each`を呼んでいます。
ブロック変数`svr`は配列要素の`接続要求を感知したソケットオブジェクト`そのものです。

その次、`if sock = accept_client(svr)`という行では`WEBrick::GenericServer#accept_client`メソッドを呼んでいます。
`WEBrick::GenericServer#accept_client`メソッドの実装を読んでみましょう。

```ruby
class WEBrick
  # ...
  class GenericServer
    # ...
    def accept_client(svr)
      case sock = svr.to_io.accept_nonblock(exception: false)
      when :wait_readable
        nil
      else
        # ...
        sock
      end
    # ...
```

引数`svr`は先ほどの「接続要求を感知したソケットオブジェクト」です。
この「接続要求を感知したソケットオブジェクト」は`TCPServer`クラスのインスタンスなのでした。

これに対して`TCPServer#accept_nonblock`メソッドを呼んでいます。

- 参照: [instance method TCPServer#accept_nonblock](https://docs.ruby-lang.org/ja/2.7.0/method/TCPServer/i/accept_nonblock.html)

`TCPServer#accept_nonblock`メソッドは、レシーバーのソケットオブジェクトをノンブロッキングモードに設定した後、クライアントからの接続要求を受け付けます。
無事に接続が完了した場合は、実際にクライアントと接続済みのソケットオブジェクト（先ほど接続要求を感知したソケットオブジェクトとは"別の"`TCPSocket`のインスタンス）を返します。

変数`sock`には、この接続済みソケットオブジェクトが代入されます。
この後case文は`else`に処理が流れ、最終的には変数`sock`（つまり接続済みソケットオブジェクト）が返されます。

`WEBrick::GenericServer#start`メソッドに戻りましょう。

```ruby
                  if sock = accept_client(svr)
                    # ...
                    th = start_thread(sock, &block)
                    # ...
                  else
                # ...
```

変数`sock`には先ほどの接続済みソケットオブジェクトが入っています。

次の行では、これを`WEBrick::GenericServer#start_thread`メソッドの引数に渡し、その返り値を変数`th`に代入しています。
（ちなみに、今回ブロックは渡していないので引数の`block`は存在しません）

`WEBrick::GenericServer#start_thread`メソッドを読んでみましょう。

```ruby
class WEBrick
  # ...
  class GenericServer
    # ...
    def start_thread(sock, &block)
      Thread.start{
        begin
          Thread.current[:WEBrickSocket] = sock
          # ...
          block ? block.call(sock) : run(sock)
          # この後はエラー処理と終了処理が続く
        end
      }
    end
```

引数`sock`は接続できた`TCPSocket`のインスタンス、
引数`block`は空です。

ここでは`Thread.start`を呼んでいます。

- 参照: [singleton method Thread.fork](https://docs.ruby-lang.org/ja/2.7.0/method/Thread/s/fork.html)
  - `Thread.start`は`Thread.fork`のエイリアスです

これによってここで新しいスレッドを生成し、その後の処理はこのスレッドの中（つまり、`Thread.start`のブロックの中）で行われるようになります。

ブロックの中を見てみましょう。
`Thread.current[:WEBrickSocket] = sock`で、現在のスレッドに固有データのキー`WEBrickSocket`を与え、その値に接続済みソケットオブジェクトを与えています。

次の行、`block ? block.call(sock) : run(sock)`において、`block`は空なので、`run(sock)`にフォールバックします。

`WEBrick::GenericServer#run`メソッドを見てみましょう。

```ruby 
class WEBrick
  # ...
  class GenericServer
    # ...
    def run(sock)
      @logger.fatal "run() must be provided by user."
    end
```

おっと、`#run`メソッドの実態は`WEBrick::GenericServer`クラスには実装されていないようです。

ここで、そもそも最初に`WEBrick::GenericServer#start`が呼び出されたレシーバーのクラスである`WEBrick::HTTPServer`クラスを見てみましょう。
`WEBrick::HTTPServer`クラスの方に`#run`メソッドが定義されています。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def run(sock)
      while true
        req = create_request(@config)
        res = create_response(@config)
        server = self
        begin
          # ...
          req.parse(sock)
          res.request_method = req.request_method
          res.request_uri = req.request_uri
          res.request_http_version = req.http_version
          res.keep_alive = req.keep_alive?
          # ...
          server.service(req, res)
          # この後はエラー処理・終了処理・その他の処理が続く
```

引数`sock`は接続済みソケットオブジェクトです。

長いので詳しくは見ませんが、ざっくりいうと
変数`req`にはHTTPリクエストの概念を扱う`WEBrick::HTTPRequest`クラスのインスタンス、
変数`res`にはHTTPレスポンスの概念を扱う`WEBrick::HTTPResponse`クラスのインスタンス、
変数`server`には`WEBrick::HTTPServer`クラスのインスタンス自身（サンプルコードの変数`srv`と同じ）が代入されます。

- 参照: [webrick/lib/webrick/httprequest.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/httprequest.rb)
- 参照: [webrick/lib/webrick/httpresponse.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/httpresponse.rb)

ここで接続済みソケットオブジェクトからリクエストの情報が読み取られ、クライアントに返すレスポンスの概念が構築されていきます。

そして、最後に`server.service(req, res)`が実行されます。
`WEBrick::HTTPServer#service`メソッドを読んでみましょう。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def service(req, res)
      # ...
      servlet, options, script_name, path_info = search_servlet(req.path)
      # ...
      si = servlet.get_instance(self, *options)
      # ...
      si.service(req, res)
    end
```

まずは`WEBrick::HTTPServer#service#search_servlet`メソッドの内容を確認しましょう。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def search_servlet(path)
      script_name, path_info = @mount_tab.scan(path)
      servlet, options = @mount_tab[script_name]
      if servlet
        [ servlet, options, script_name, path_info ]
      end
    end
```

引数`path`はリクエストパスである`'view.cgi'`です。

ここでインスタンス変数`@mount_tab`が再登場します。
インスタンス変数`@mount_tab`の中身は`WEBrick::HTTPServer::Mountable`クラスのインスタンスでした。

`WEBrick::HTTPServer::Mountable#scan`メソッドを見てみましょう。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      # ...
      def scan(path)
        @scanner =~ path
        [ $&, $' ]
      end
```

引数`path`の中身はリクエストパス`'/view.cgi'`、
インスタンス変数`@scanner`の中身は`/\A(\/view\.cgi)(?=\/|\z)/`でした。

ここでは、リクエストパス`'/view.cgi'`と`/\A(view\.cgi)(?=\/|\z)/`の正規表現マッチを行い、次のような配列を返しています。

```ruby 
['/view.cgi'(最後にマッチした文字列), ''(最後にマッチした文字列より後ろの文字列)]
```

`WEBrick::HTTPServer#service#search_servlet`に戻ると、
変数`script_name`に`'/view.cgi'`、
変数`path_info`に空文字列`''`が格納されていることがわかります。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def search_servlet(path)
      script_name, path_info = @mount_tab.scan(path)
      servlet, options = @mount_tab[script_name]
      if servlet
        [ servlet, options, script_name, path_info ]
      end
    end
```

続いて、次の行で呼ばれているのは`@mount_tab[script_name]`です。

`WEBrick::HTTPServer::Mountable`クラスには`#[]`メソッドが定義されています。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    class MountTable # :nodoc:
      # ...
      def [](dir)
        dir = normalize(dir)
        @tab[dir]
      end
```

引数`dir`は先ほどの`'/view.cgi'`です。

インスタンス変数`@tab`は`{ '/view.cgi' => [WEBrick::HTTPServlet::CGIHandler, 'view.rb'] }`というハッシュなので、キー`'/view.cgi'`を渡すと値である配列`[WEBrick::HTTPServlet::CGIHandler, 'view.rb']`を返します。

`WEBrick::HTTPServer#service#search_servlet`に戻ると、
変数`servlet`に`WEBrick::HTTPServlet::CGIHandler`、
変数`options`に`'view.rb'`が格納されていることがわかります。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def search_servlet(path)
      script_name, path_info = @mount_tab.scan(path)
      servlet, options = @mount_tab[script_name]
      if servlet
        [ servlet, options, script_name, path_info ]
      end
    end
```
これで、事前にマウントしておいたサーブレットクラスを取り出すことができました。

`servlet`はtruthyなためにif節の中が評価され、最終的に`WEBrick::HTTPServer#service#search_servlet`の返り値はこのような配列になります。

```ruby
[WEBrick::HTTPServlet::CGIHandler, 'view.rb', '/view.cgi', '']
```

それでは`WEBrick::HTTPServer#service`に戻ります。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def service(req, res)
      # ...
      servlet, options, script_name, path_info = search_servlet(req.path)
      # ...
      si = servlet.get_instance(self, *options)
      # ...
      si.service(req, res)
    end
```
先ほどの`WEBrick::HTTPServer#service#search_servlet`の返り値がそのまま変数`servlet`、`options`、`script_name`、`path_info`に格納されています。

次の行`servlet.get_instance`のレシーバーは`WEBrick::HTTPServlet::CGIHandler`クラスですが、`.get_instance`メソッドはそのスーパークラスである`WEBrick::HTTPServlet::AbstractServlet`クラスに実装されています。

メソッド名を見るからにサーブレットクラスのインスタンスの生成を行なっていそうです。
その下には、サーブレットクラスのインスタンスに`#service`メソッドを呼ぶ行もあります。

ということで、 **④ パスに対してリクエストが発生** で読む処理はここまでとし、続きは最後の **⑤ サーブレットが処理を実行** で読むこととします。

### まとめ
ここまでで行われた処理は以下の通りです。

- 接続要求待ちだったソケットオブジェクトがクライアントからの接続要求を感知
- 接続要求を感知したソケットオブジェクトが接続要求を受け入れ、接続済みソケットオブジェクトを返す
- サーバープロセスが新しいスレッドを生成、以降の処理は新しいスレッド内で実行
- 接続済みソケットオブジェクトからリクエストの情報を読み出し、レスポンスの構築を開始
- リクエストパス（`'/view.cgi'`）を元に、インスタンス変数`@mount_tab`から事前にマウントしておいたサーブレット（`WEBrick::HTTPServlet::CGIHandler`クラス）とサーブレットに渡す引数（`'view.rb'`）を取得する

WEBrickコードリーディングもいよいよ大詰めです。

## ⑤ サーブレットが処理を実行
先ほどの`WEBrick::HTTPServer#service`をもう一度見ておきましょう。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def service(req, res)
      # ...
      servlet, options, script_name, path_info = search_servlet(req.path)
      # ...
      si = servlet.get_instance(self, *options)
      # ...
      si.service(req, res)
    end
```
最初の行で今回実行するサーブレットを特定することができたので、続いて`si = servlet.get_instance(self, *options)`の行を確認していきます。

`WEBrick::HTTPServlet::AbstractServlet.get_instance`を見てみましょう。

- 参照: [webrick/lib/webrick/httpservlet/abstract.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/httpservlet/abstract.rb)

```ruby
module WEBrick
  module HTTPServlet
    # ...
    class AbstractServlet
      # ...
      def self.get_instance(server, *options)
        self.new(server, *options)
      end

      # ...

      def initialize(server, *options)
        @server = @config = server
        # ...
        @options = options
      end

      # ...
```

`WEBrick::HTTPServlet::AbstractServlet.get_instance`メソッドは、その名の通り自身のインスタンスを生成するのみの役割を担っています。

そのすぐ下に`WEBrick::HTTPServlet::AbstractServlet.initialize`メソッドが定義されており、実際にはここでインスタンスが初期化されています。

ところで、元々のコードで`.get_instance`メソッドのレシーバーになっていたのは`WEBrick::HTTPServlet::AbstractServlet`クラスのサブクラスである`WEBrick::HTTPServlet::CGIHandler`クラスでした。

`WEBrick::HTTPServlet::CGIHandler`クラスの`#initialize`メソッドも見ておきましょう。

- 抜粋: [webrick/lib/webrick/httpservlet/cgihandler.rb](https://github.com/ruby/webrick/blob/master/lib/webrick/httpservlet/cgihandler.rb)

```ruby
module WEBrick
  module HTTPServlet
    # ...
    class CGIHandler < AbstractServlet
      # ...
      def initialize(server, name)
        super(server, name)
        @script_filename = name
        @tempdir = server[:TempDir]
        interpreter = server[:CGIInterpreter]
        if interpreter.is_a?(Array)
          @cgicmd = CGIRunnerArray + interpreter
        else
          @cgicmd = "#{CGIRunner} #{interpreter}"
        end
      end

      # ...
```

先ほどスーパークラスである`WEBrick::HTTPServlet::AbstractServlet`クラスで行なった初期化処理に加え、`WEBrick::HTTPServlet::CGIHandler`クラスに特有の初期化処理を行なっているようです。

サーブレットクラスのインスタンス化が終わったので、`WEBrick::HTTPServer#service`メソッドに戻ります。

```ruby
module WEBrick
  class HTTPServer < ::WEBrick::GenericServer
    # ...
    def service(req, res)
      # ...
      si = servlet.get_instance(self, *options)
      # ...
      si.service(req, res)
    end
```

最終行で、先ほど生成した`WEBrick::HTTPServlet::CGIHandler`クラスのインスタンスに対して`#service`メソッドを呼んでいます。

この`#service`メソッドは、`.get_instance`メソッドと同じく`WEBrick::HTTPServlet::AbstractServlet`クラスに実装されています。

```ruby
module WEBrick
  module HTTPServlet
    # ...
    class AbstractServlet
      # ...
      def service(req, res)
        method_name = "do_" + req.request_method.gsub(/-/, "_")
        if respond_to?(method_name)
          __send__(method_name, req, res)
        else
          raise HTTPStatus::MethodNotAllowed,
                "unsupported method `#{req.request_method}'."
        end
      end

      # ...

      def do_GET(req, res)
        raise HTTPStatus::NotFound, "not found."
      end
```

引数`req`には`WEBrick::HTTPRequest`クラスのインスタンス、
引数`res`には`WEBrick::HTTPResponse`クラスのインスタンスが格納されています。

最初の行で呼ばれている`req.request_method`には、HTTPリクエストメソッドを示す文字列が入っています。例えば`GET`や`POST`などです。

ここでは例示のため、`req.request_method`を呼んだ結果`'GET'`という文字列が返ってきたことにしましょう。
一行目を実行すると、変数`method_name`には`'do_GET'`のような文字列が格納されます。

次の条件分岐で、自身（`WEBrick::HTTPServlet::CGIHandler`クラスのインスタンス）がこの文字列 `'do_GET'`と同じ名前のメソッドを持っているかどうかを確認し、`true`の場合はこのメソッドを`__send__`メソッドで呼び出します。


- 参照: [instance method BasicObject#\_\_send__](https://docs.ruby-lang.org/ja/2.7.0/method/BasicObject/i/__send__.html)

`#do_GET`メソッドはそのすぐ下に定義されていますが、その中身はこの`WEBrick::HTTPServlet::AbstractServlet`クラスではなく、元々呼んでいた`WEBrick::HTTPServlet::CGIHandler`クラスに実装されています。

`WEBrick::HTTPServlet::CGIHandler#do_GET`メソッドはこちらです。

```ruby
module WEBrick
  module HTTPServlet
    # ...
    class CGIHandler < AbstractServlet
      # ...
      def do_GET(req, res)
        cgi_in = IO::popen(@cgicmd, "wb")
        cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
        cgi_out.set_encoding("ASCII-8BIT")
        cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
        cgi_err.set_encoding("ASCII-8BIT")
        begin
          cgi_in.sync = true
          meta = req.meta_vars
          meta["SCRIPT_FILENAME"] = @script_filename
          meta["PATH"] = @config[:CGIPathEnv]
          meta.delete("HTTP_PROXY")
          if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
            meta["SystemRoot"] = ENV["SystemRoot"]
          end
          dump = Marshal.dump(meta)

          cgi_in.write("%8d" % cgi_out.path.bytesize)
          cgi_in.write(cgi_out.path)
          cgi_in.write("%8d" % cgi_err.path.bytesize)
          cgi_in.write(cgi_err.path)
          cgi_in.write("%8d" % dump.bytesize)
          cgi_in.write(dump)

          req.body { |chunk| cgi_in.write(chunk) }
          # この後は終了処理が続く
```

引数`req`には`WEBrick::HTTPRequest`クラスのインスタンス、
引数`res`には`WEBrick::HTTPResponse`クラスのインスタンスが格納されています。

ここまで読んでおいて申し訳ないのですが、（すでにお気づきかとは思いますが）読み手であるわたし自身がCGIの仕組みに明るくなく、具体的にここでどのような処理を行なっているのかの詳細をお伝えすることができません。

ただし、一行目で`IO.popen`を呼んでいることから、forkして子プロセスでインスタンス変数`@cgicmd`に格納されたシェルコマンドを実行し、子プロセスの標準入力へつながる入力用パイプ（`IO`クラスのインスタンス）を変数`cgi_in`に代入していること、

- 参照: [singleton method IO.popen](https://docs.ruby-lang.org/ja/2.7.0/method/IO/s/popen.html)

そして最後の行でチャンク化したリクエストボディをこの子プロセスの標準入力へつながる入力用パイプへ書き込んでいることが伺えます。

- 参照: [singleton method IO.write](https://docs.ruby-lang.org/ja/2.7.0/method/IO/s/write.html)

`WEBrick::HTTPServlet::CGIHandler`クラスには、この`#do_GET`メソッド以外にも、各HTTPメソッドに対応したインスタンスメソッドが記述されています。
それは他のサーブレットクラスも同じです。
`WEBrick::HTTPServlet::AbstractServlet#service`メソッドは、リクエストメソッドに応じて適切なサーバー処理を行うことができるよう、処理を振り分けていたのです。

### まとめ
ここまでで行われた処理は以下の通りです。

- サーブレットクラスである`WEBrick::HTTPServlet::CGIHandler`クラスのインスタンスを生成
- `WEBrick::HTTPServlet::CGIHandler`クラスのインスタンスに対して`#service`メソッドを実行
  - リクエストメソッドに応じて処理を振り分け、それぞれ適切なサーバー処理を実行する

WEBrickをめぐるコードリーディングはここでおしまいです。お疲れ様でした🙌

## おわりに
最初にお断りを入れた通り、今回読む対象の機能を絞るためかなりのソースコードを省略してご紹介していますが、もちろん今回取り上げた以外にもWEBrickにはHTTPサーバーの機構を叶える様々な仕掛けが仕込まれています。
Webアプリケーション開発者である自分にとって、普段はアプリケーションサーバーとして身近な存在であるWEBrickですが、Webの仕組みを学ぶコードリーディングの題材としてもとても面白く、読み応えのあるライブラリだと感じました。

しかしながらわたしははまだまだ勉強中の身ですので、この記事の内容への技術的指摘、アドバイス等ありましたら教えていただけると幸いです。

最後になりますが、この記事は[Tama.rb OSSコードリーディング部 WEBrick編](https://tamarb.connpass.com/event/194504/)の開催を抜きに書き上げることはできませんでした。
イベントの開催を主導してくださった大倉雅史さん @okuramasafumi 、そしてイベントへの参加を快く引き受けてくださり、当日も興味深いエピソードや示唆に富むコードの読み方を教えてくださった高橋征義さん @takahashim へ心から感謝します。本当にありがとうございました。

それでは皆さん、良いクリスマスをお過ごしください🎄✨