そういや一年前、「ウェブサーバーのこと知りたい -> Rack読もう。」となって挫折した。少しずつ暖かくなって意識が高まってきたし、今やっている仕事がずいぶんと(すくなくともRailsアプリ書くのと比較すると)低レイヤーなところなので、良い感じの相乗効果を狙って今のうちにサーバーについて理解を深めておきたい。そういうわけで、今春はWebrick読もう。
読みかた
今度は挫折したくない。以下の手法に従って読む。
また、Cのソースにも触れることになると思うので以下も参考にする。
使うもの
動的解析
基本的に解析は動的解析から始めるのがよい。 静的解析とは、多かれ少なかれ、プログラムの動作を予想することである。 対して動的解析で見るのは事実である。 まず事実を見ておいたほうが方向付けがしやすいし、間違いも減る。 最適化する前にプロファイルを取れ、というのと似ているだろうか。 事件解決はまず現場から、というのでもよい。
とのこと。まずは動かす。
サーバー起動
ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start'
なお、これは以下と同等(r オプション使うの初めてだ)。
ruby -e 'require "webrick";WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start '
[2016-03-26 14:03:45] INFO WEBrick 1.3.1
[2016-03-26 14:03:45] INFO ruby 2.2.3 (2015-08-18) [x86_64-darwin14]
[2016-03-26 14:03:45] INFO WEBrick::HTTPServer#start: pid=4563 port=8000
参考:ワンライナーWebサーバを集めてみた - Qiita
リクエストを投げる
クライアントとして、別のシェルを開いて以下を実行する。
curl localhost:8000
(クライアント側)
$ curl localhost:8000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>Index of /</TITLE>
<style type="text/css">
...(以下略)
(サーバー側)
localhost - - [26/Mar/2016:14:04:04 JST] "GET / HTTP/1.1" 200 4076
- -
クライアントからHTTPリクエストを投げたらちゃんとハイパーテキストを返してくれたので、ウェブサーバーとして機能したと言える。サーバー側のログには時刻やHTTPメソッド・プロトコルのバージョン・ステータスなどが出されている。
静的解析
前準備
さすがにGithubのウェブサイト上でファイルを一つ一つブラウジングして理解できる気がしないので、「ソースコード完全解説ガイド」に従っていくつかの前準備を行う。
ドキュメントを読む
今回は http://docs.ruby-lang.org の説明を参照する。
汎用HTTPサーバーフレームワークです。HTTPサーバが簡単に作れます。
WEBrick はサーブレットによって機能します。サーブレットとは サーバの機能をオブジェクト化したものです。 ファイルを読み込んで返す・forkしてスクリプトを実行する・テンプレートを適用する など、「サーバが行なっている様々なこと」を抽象化しオブジェクトにしたものが サーブレットです。サーブレットは WEBrick::HTTPServlet::AbstractServlet の サブクラスのインスタンスとして実装されます。
WEBrick はセッション管理の機能を提供しません。
Webrick はRuby の標準ライブラリなので、Githubの ruby リポジトリから入手する。
git clone git@github.com:ruby/ruby.git
tags ファイルつくる
webrickはlib/下にある。そこでctagsコマンドを実行する。
cd ruby/lib/webrick
ctags -R
参考:ctagsと連携するように環境を構築する - Qiita
ファイル構成を見る
MacBook-Pro:webrick gaaamii$ tree .
.
├── accesslog.rb
├── cgi.rb
├── compat.rb
├── config.rb
├── cookie.rb
├── htmlutils.rb
├── httpauth
│ ├── authenticator.rb
│ ├── basicauth.rb
│ ├── digestauth.rb
│ ├── htdigest.rb
│ ├── htgroup.rb
│ ├── htpasswd.rb
│ └── userdb.rb
├── httpauth.rb
├── httpproxy.rb
├── httprequest.rb
├── httpresponse.rb
├── https.rb
├── httpserver.rb
├── httpservlet
│ ├── abstract.rb
│ ├── cgi_runner.rb
│ ├── cgihandler.rb
│ ├── erbhandler.rb
│ ├── filehandler.rb
│ └── prochandler.rb
├── httpservlet.rb
├── httpstatus.rb
├── httputils.rb
├── httpversion.rb
├── log.rb
├── server.rb
├── ssl.rb
├── tags
├── utils.rb
└── version.rb
読むの開始
先ほどのワンライナーをふたたび見る。
WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start
まずは以下からざっくりと追う。
- HTTPServer.new
- HTTPServer#start
HTTPServer.new
インスタンス初期化時に何をしているのか。mount
というメソッドでパスにサーブレットを割り当てているようだ。あとは @virtual_hosts
という配列を初期化している。
HTTPServer#start
HTTPServerのインスタンスメソッドstart
見ようとしても、ない。継承元のGenericServer
のものだとわかる。
GenericServer#start
さて、このstartメソッドは何をしてるのか。コメントがあるので、それを見る。
137 ##
138 # Starts the server and runs the +block+ for each connection. This method
139 # does not return until the server is stopped from a signal handler or
140 # another thread using #stop or #shutdown.
141 #
142 # If the block raises a subclass of StandardError the exception is logged
143 # and ignored. If an IOError or Errno::EBADF exception is raised the
144 # exception is ignored. If an Exception subclass is raised the exception
145 # is logged and re-raised which stops the server.
146 #
147 # To completely shut down a server call #shutdown from ensure:
148 #
149 # server = WEBrick::GenericServer.new
150 # # or WEBrick::HTTPServer.new
151 #
152 # begin
153 # server.start
154 # ensure
155 # server.shutdown
156 # end
意訳する。
「サーバーを起動してコネクション毎にブロックを実行する。このメソッドはシグナルハンドラあるいは他のスレッドの#stop
か#shutdown
によってサーバーが停止するまでreturnしない。(以降、例外捕捉の説明)」
スレッド <--(コネクション)--> クライアント
スレッド <--(コネクション)--> クライアント
スレッド <--(コネクション)--> クライアント
という感じで、スレッドとコネクションとクライアントが1対1対1の関係になるのがこのstartメソッドを見るとわかる。
この処理はbegin節で囲まれてた中で無限ループしていて、先のコメントにあったようにシグナルハンドラなんかによって停止されたときにこの節を抜ける。するとensure節が実行されて、サーバーが停止する。
というわけで、実際のコードの中身について、大事そうなものをかいつまんで見てみていく。
- server_type.start
- setup_shutdown_pipe
- svrs = IO.select([sp, *@listeners], nil, nil, 2.0)
- sock = accept_client(svr)
- th = start_thread(sock, &block)
- thgroup.add(th)
server_type.start
server_type にはデフォルトではWEBrick::SimpleServerとなるので、このstartメソッドをみる。
(server.rb)
27 class SimpleServer
28
29
30
31
32 def SimpleServer.start
33 yield
34 end
35 end
yield
の一行しかない。渡したブロックそのまま実行するということがわかる。
setup_shutdown_pipe
よくわからない。パイプってなんだ(あとでみる)
svrs = IO.select([sp, *@listeners], nil, nil, 2.0)
IO.selectが何をしているのか。Rubyの組み込みライブラリなので、まずドキュメントにあたった方がよさそうだ。
http://docs.ruby-lang.org/ja/2.2.0/class/IO.html
IOクラスとは、
基本的な入出力機能のためのクラスです。
とのこと。では、このselectというメソッドはなにか。
http://docs.ruby-lang.org/ja/2.2.0/class/IO.html#S_SELECT
与えられた入力/出力/例外待ちの IO オブジェクトの中から準備ができたものを それぞれ配列にして、配列の配列として返します。 タイムアウトした時には nil を返します。
ソースも見てみる。IOクラスはwebrickの中には見つからないので、今回は pry-doc というgemを使ってpry(対話環境)から場所を見つけることにする。
[1] pry(main)> show-source IO.select
From: io.c (C Method):
Owner: #<Class:IO>
Visibility: public
Number of lines: 22
static VALUE
rb_f_select(int argc, VALUE *argv, VALUE obj)
Cのソースで、rb_f_select
という関数だということがわかった。
start_thread
ThreadGroup#add
ThreadGroup の定義元に飛びたいけど、これは組み込みライブラリ、つまりCのソースになるので、webrickの中にはいない。ということで、今回は pry-doc というgemを使ってpry(対話環境)上から場所を見つけることにする。
pry> show-source ThreadGroup#add
とすると、このメソッドの定義元がわかる。どうやらthread.c にある thgroup_add
という関数らしい。
IO.select([sp, *@listeners], nil, nil, 2.0)
書き途中です。