Rack のソースコード読んでる
途中だけどあとちょっと書ききるのがめんどくさくなったので公開する。書き始めたのは今週月曜日なのにこの進捗っぷりは、バイトだったりやる気が出なかったりで。
「他人のソースコードを読むのは良い学習だ」っていうのは明らかで、行うべきだと思う。
でこのエントリを書きはじめるつい数分前まで id:shgam さんと リモートソースコード読書会的な感じで Rack のソースコードを読んでた。読書会っていってもたいそうなものじゃなくて、ハングアウトで通話しながら Cloud9 を介して、2人で一行一行を追いながら読んでいく感じ。Rack けっこう大きいし、低いレイヤーの直接触ったことがないライブラリだったので、読むのは苦労してる。「うーん……うーん……」と悩んで、コメントにある URL を開いたら CGI の仕様書のような内容でそれもちょっと読んでみたり……。
余談だけど Cloud9 すごかった。ついさっき教えられて 1~2 時間触っただけだけどなんかすごかった。リポジトリを fork してきてそれをブラウザ上でけっこうリッチな感じのエディタで開けてそれを共有するとカーソルやら選択範囲がシェアされて……、みたいな。
なぜ Rack か
Rack は Ruby を使ってる Web 開発者ならだれでもほぼ絶対お世話になっているであろうライブラリ。webrick なんかのサーバーアプリケーションと Ruby のコードをつないでくれるやつ。Ruby on Rails や Sinatra をはじめとした Ruby の web アプリケーションフレームワークは、だいたい Rack を基にして作られている(よね?)。
一番好きな言語は Ruby で Web 開発者なんだからそりゃ超基礎となる Rack の内部実装くらい知っておかないといけないだろう、ということで読み始めた。
というのはこの瞬間に考えた理由で、本当は id:shgam さんが読んでたから。idobata で read ルームを作ってそこにコードリーディングの進捗を流されていたのを見ていて、それに参加させてもらった形。
コードリーディング
Rack は 2015年4月21日 master ブランチ最新版のもの。
どこから読むか
だいたい実行する末端から読んでいってる。 rack は実行方法が 2 つあって、コード中で require してメソッドを呼ぶか、コマンドラインから ru ファイルを呼び出して実行するか。ru は rackup の略だと思う。名前だいじ。
今回はコマンドラインから実行する方で追っていく。
コードの中で実行
require 'rack' app = Proc.new do |env| ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] end Rack::Handler::WEBrick.run app
コマンドラインから
config.ru
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['get rack\'d']] }
$ rackup config.ru
bin/rackup
Gem の bin ディレクトリに入ってるものは、マシンに実行ファイルとしてコピーされる。今回でいうと rackup コマンドで実行するので、bin/rackup を読む。
#!/usr/bin/env ruby require "rack" Rack::Server.start
require "rack"
で lib/rack を読み込んでる。で、Rack::Server.start
を実行してる。Rack::Server は lib/rack/server.rb に記述してある。
lib/rack/server.rb
146行目から
def self.start(options = nil) new(options).start end
Rack::Server インスタンスを生成して、それの start
メソッドを呼んでる。インスタンスが生成されているので Rack::Server.initialize
を見に行こう。
184 行目から
def initialize(options = nil) @ignore_options = [] if options @use_default_options = false @options = options @app = options[:app] if options[:app] else argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV @use_default_options = true @options = parse_options(argv) end end
ここ悩んだ。 options が引数として定義されてるんだけど、Rack::Server のインスタンスメソッドとして options
メソッドが定義してあって、「あれ?この場合って options は 引数の内容なの? それとも options メソッドを見に行くの? どっち?」ってなった。以下のコードを実行すると false が出力されたので、この場合の options は引数のもので内容はデフォルトの nil だ、ということで進めた。
class Dev def initialize(options = nil) if options p true else p false end end def options true end end Dev.new # => false
else のブロックに入ってさっそくこのコードで詰まった。
argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV
まず defined?
を知らなかったので調べた。こういうもの。式が定義されていれば式の種類の文字列を、定義されていなければ false を返す。? で終わるメソッドなんだから Boolean で返せよ、って思った。
a = true b = nil defined? a #=> "local-variable" defined? b #=> "local-variable" defined? c #=> nil
defined? が分かったはいいが今度は SPEC_ARGV
がわからなかった。リポジトリ全体で検索してみると、SPEC_ARGV はテストファイルでのみ使われていてテスト用に定義してある ARGV だということがわかった。
三項演算子なので SPEC_ARGV が定義されていれば argv = SPEC_ARGV
、定義されていなければ argv = ARGV
ということになる。テスト環境以外ではコマンドライン引数の配列 ARGV が代入されるようだ。
で、@use_default_options
に true
が入る。
次は @options
に parse_options(argv)
の戻り値を代入する。argv の内容は先ほどでも述べたとおりコマンドライン実行だと ARGV だ。では parse_options()
のコードを見ていこう。