読者です 読者をやめる 読者になる 読者になる

gaaamiiのブログ

悪気なく間違ったことを書いている時があります。コメントやTwitter、ブコメなどでご指摘ください

Ruby製のウェブサーバライブラリWebrickのソースコードを読む

そういや一年前、「ウェブサーバーのこと知りたい -> 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

リクエストを投げる

クライアントとして、別のシェルを開いて以下を実行する。

https://i.gyazo.com/10287afed859f10d8aa06f1fa223d8c7.gif

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 はセッション管理の機能を提供しません。

ソースコード入手

WebrickRuby の標準ライブラリなので、Githubruby リポジトリから入手する。

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節が実行されて、サーバーが停止する。

というわけで、実際のコードの中身について、大事そうなものをかいつまんで見てみていく。

  1. server_type.start
    1. setup_shutdown_pipe
    2. svrs = IO.select([sp, *@listeners], nil, nil, 2.0)
    3. sock = accept_client(svr)
    4. th = start_thread(sock, &block)
    5. thgroup.add(th)
server_type.start

server_type にはデフォルトではWEBrick::SimpleServerとなるので、このstartメソッドをみる。

(server.rb)
 27   class SimpleServer       
 28 
 29     ##
 30     # A SimpleServer only yields when you start it
 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)

書き途中です。

The Art of Readable Code 読んでる

読んでる

感想

至極真っ当なことが書かれていて、そうですよねえ。そうですよねえ。と頷きながら読める。変数名はわかりやすいものを付けるべきだ。細々した問題が度々出てくるようならそれに対処する小さな関数を定義して本体のプログラムは関心を明確に保つべきだ。そういうのはコードに対する美意識以前の、最低限のスキルなんだろうと思う。

けれども現実では、ワードでドキュメント書いたりエクセルにデータ手入力するのと同じ感覚でプログラムを書く人たちも世の中にはたくさんいて、そういう人たちが日本のソフトウェア産業を担ってしまっている。そもそもコードに対して注意を払う必要があるのは、考えずに書けばコードは汚くなるものだからだと思う。コードが汚くなれば、読む人は苦しみ、迫る納期に押し潰されそうにながら破綻ぎりぎりのところで仕様変更や機能追加に対応せざるを得なくなる。

そういう理想と現実のギャップみたいなものを感じながら本書を読んでいます。

スレッドってなんだ

まとめていくかもしれない。

背景

  • 仕事で扱ってるはずなのによくわかってない部分がある
  • セマフォミューテックスってなんだって5回くらい思い返してる
  • OSがなにしてるのかよくわからん
  • CPUやらメモリのこと、ひいてはコンピューターアーキテクチャよくわからん

なんとなくわかっている(感じている)こと

  • スレッドは同一プロセス内で複数動かすことが出来る(そのときリソース(メモリの領域?)取り合っておかしくならないようにセマフォやらミューテックスが必要になるらしい)
  • 以前枕元に置いておいて結局あまり読み進められなかったUNIX詳解プログラミングにいろいろ書いてあったはず
  • マルチスレッドでいろいろ動くやつはテストするのがつらい(?)
  • ちゃんと知っている人は偉い

簡潔な説明

GLib Reference

Threads act almost like processes, but unlike processes all threads of one process share the same memory.

Wikipedia

In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.

In computer architecture, multithreading is the ability of a central processing unit (CPU) or a single core in a multi-core processor to execute multiple processes or threads concurrently, appropriately supported by the operating system.

これのために手を伸ばしている本

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版

「アプレンティスシップ・パターン」というプログラマ向け自己啓発書を読んでる

本屋でプログラマ向け自己啓発書みたいなのを3冊買ってきました。

この中の一冊、「アプレンティスシップ・パターン」を今読んでいます。今年四月から社会人生活が始まり初めての現場で半年を過ごした自分にとって、身の振り方を考えるために役立ちそうだと感じました。

誰のための本か

本書のまえがきには以下のように書かれています。

ソフトウェア開発を経験したことがあり、卓越したソフトウェア開発者になることを切望している人のために、本書は書かれています。

また、序論ではこう書かれています。

本書は、ソフトウェアのアプレンティス向け、つまり、ソフトウェア開発を経験したことがあり、さらに先に進みたいが、指導を必要としている人々向けです。

出だしにこんな魅力的なことが書かれていたら、もう読むしかない。僕もいつかは卓越したソフトウェア開発者になりたいので、読むことにしました。

パターン

ある状況下での問題を繰り返し解決している方法に名前を付けた説明がパターンです(本書「まえがき」より)。各パターンは状況・問題・解決方法・行動の4つから構成されていて、そのパターンの「行動」の段落を読めば、すぐに実践出来る形になっています。

パターンの中にはすでに聞いたことがあるようなものもあります。たとえば、「Be the Worst(最低であれ)」は、「情熱プログラマー」でも紹介されている有名な助言です。id:karur4n さんがはてなインターンに行って周りが優秀な人ばっかで落ち込んでいる時にこの言葉を知っていたら、「Be the worst を実践してて素晴らしいですね」みたいな使い方ができたと思う。というか、似たような話をどこかのくもキャストでしていた気がする。

他にも、すでに聞いたような話がある。「白帯」というパターンは、何か新しいことを学ぶときに、これまでの知識や経験を一度捨てることの重要性を説いていますが、これは以前 id:yss44 さんにどうやって学習しているのか尋ねた時に教えてもらったことと被ります。

つまり、読めばどれも聞いたことのあるような話ではある。けれども、その1つ1つにしっかり名前を付けてパターンとしてまとめているのが良い。

プログラミング以外の場面でも「これって意味あるのかな」と思うような微妙なイベントに対して、パターンと照らし合わせながら判断ができるようになりそうです。学習をしている途中で「このやり方でいいのか?」と立ち止まらないために本書のパターンを活用していきたいです。

プログラミング能力の獲得にコミュニケーションは必要なのか

中でも特に気になったのが、勉強会やコミュニティ、人との交流がプログラミング能力の獲得に与える影響です。これだけに着目して書かれたようなパターンがあるわけではありませんが、いくつかのパターンは僕が以前から持っていた疑問をずいぶん解消してくれました。


書き途中です。

「Effective Ruby」を読んで例外について学んでる

読んでる。

なんで例外の章を読むのか

Yomomoをえっさほいさちょっとずつ作りこんでいて、ああトップページもっときれいにしたいとかiPhoneアプリ欲しいとか、そういうキラキラしたことについてはすごい楽しく頻繁に考える。でもアプリケーションの不具合についてはだいたい見つかってから直したりしがちで、その直し方についてもその場しのぎっぽいものが多い。

はやくどうにかしなきゃと焦ってコミットしたときのやつ

やっぱりできる大人といえばみんな例外をわきまえてる(きっと)。これとこれとこれとこういう例外があり得ますよね、ということはそれをこういう順序でどこで待ち構えてれば大丈夫ですよね、というのが分かっていれば、しっかりした大人の職業プログラマーの道へと進めるんじゃないだろうか。

そういう動機で例外の章を読むことにした。

Effective Ruby

Effective Ruby

例外ってなんだ

そもそも例外ってなんだ。この本ではこう述べられている。

例外は、2つの異なる言語機能を1つにまとめたものだと考えられる。2つとは、エラーの説明と制御フローである。

なるほど。中身をなぞるだけではつまらないので、実際にyomomoの以前のコードを例として使いながらこの第5章の内容を追ってみる。

エラーの説明

著者は想定していないだろうけど世の中は広いので、例外をつかまえずエラーの説明をログに吐かない糞な個人開発者もいる。僕です。

しかしそんなやつでも使っているのがrescueだ。何も考えずに利用していたけれど、ここでしっかりと正しい理解を獲得しておきたい。

例外について考えたこともない人がrescueを使うとどうなるか

例外について考えたこともない人がrescueを使うとどうなるか、という悪い例がある。以下はYomomoでこの前まで使っていたメソッドだ。Amazon Product Advertising API へのアクセスに失敗した時に、試行回数の上限に達するまでアクセスを繰り返す。

  def get_books(asins, search_params, trial_limit = 2)
    trial = 0
    begin
      return Amazon::Ecs.item_lookup(asins, search_params).items
    rescue => e
      p e
      trial += 1
      retry if trial <= trial_limit
      return false
    end
  end

見ての通り、「エラーの説明」にあたる部分がない。「アクセスに失敗してそこで落ちてしまっては困るので、とりあえずもう二回まではやってみてね」というのをそのまま書いただけの状態だ。

ここで使っているamazon-ecsというgemにはちゃんと例外が用意されているので、それを使ってログを吐くようにしておくべきだ。

    rescue Amazon::RequestError => e
      log.error(e)
      trial += 1
      retry if trial <= trial_limit
      return false
    rescue => e
      log.error(e)
    end 

自分が書いた上記のような例ほどひどいものを書く人は少ないかもしれないけど、例外のクラスを指定せずに受け取って処理する以下のようなコードについては著者も懸念していて、これのまずさについて丁寧に説明している。

begin
  task.perform
rescue => e
  logger.error(e)
end

ご存知のように、このような形でrescueを使うのは、StandardErrorとそのあらゆるサブクラスを処理するという意味である。

例外の定義とraise

自分でクラスやモジュールを定義するようなときには、上でいうAmazon::RequestErrorのような例外をどう定義して、いつ発生させるかということについて考えなければいけない。

制御フロー

例外をどう使うか、どう定義するか、どう発生させるか、ということについてはわかった。じゃあ例外を捕まえた節で、制御フローをどう作っていけばいいのか。アプリケーションやらシステムを作る側としてはこちらの方が重大な問題だ。本章の項目26と27で、この問題について説明されている。

retry

これまた上で挙げたAmazon Product Advertising API へのアクセスの部分が例として使えそうなので、これの悪いところをベースに考えていく。

    begin
      return Amazon::Ecs.item_lookup(asins, search_params)
    rescue Amazon::RequestError => e
      log.error(e)
      trial += 1
      retry if trial <= trial_limit
      return false
    rescue => e
      log.error(e)
    end 

上でも書いたけど、ここでretryを入れた理由はAPIへのリクエストが不正だっつってエラーが起きた時に、そこで処理を終わらせないためだ。けれども、このretryの使い方にはいくつか問題がある。その問題や疑問について、この本が全て解消してくれた。

retry がよくわかってないから最初の処理でもreturnしてる

begin節で例外が吐かれなければretryしないので、最後に評価する式はAmazon::Ecs.item_lookup(asins, search_params)になる。だからreturnを明示的に書かないでもいいはず。

begin
  Amazon::Ecs.item_lookup(asins, search_params)
rescue
...
begin
  # なんかする
rescue
  # なんか失敗する
  retry
end

これは、以下に等しいらしい。

while true
  begin
    # なんかする
  rescue
    # なんか失敗する
  else
    break
  end
end

throw


書き途中です。

本を読む本を読んだ感想を書きたい

くもキャスト収録のときにきみひとさんからすすめてもらった。

あやぴーさんからの大量のおすすめ本を消化できない。また、自分で設けた1年で100冊という目標が進捗出てなくてやばい。救いを求めてこの本に手を伸ばした。

本を読む本は70年以上前の本で、読書術の古典•名著として知られてるっぽい。

本を読む本 (講談社学術文庫)

本を読む本 (講談社学術文庫)

ここに書く予定

  • 読書の4段階
  • ミルクの表面がどうのこうの
  • 寝ないように読む工夫
  • 点検読書の3規則
  • シントピカル読書ってなんだ
  • 感想ざっくり

書き途中です。

「ステートフルJavaScript」を読んだ感想書きたい

書きたい