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

大学生からの Web 開発

廃れない技術よ 我が身に

Rack のソースコード読んでる

途中だけどあとちょっと書ききるのがめんどくさくなったので公開する。書き始めたのは今週月曜日なのにこの進捗っぷりは、バイトだったりやる気が出なかったりで。

「他人のソースコードを読むのは良い学習だ」っていうのは明らかで、行うべきだと思う。

でこのエントリを書きはじめるつい数分前まで id:shgam さんと リモートソースコード読書会的な感じで Rackソースコードを読んでた。読書会っていってもたいそうなものじゃなくて、ハングアウトで通話しながら Cloud9 を介して、2人で一行一行を追いながら読んでいく感じ。Rack けっこう大きいし、低いレイヤーの直接触ったことがないライブラリだったので、読むのは苦労してる。「うーん……うーん……」と悩んで、コメントにある URL を開いたら CGI仕様書のような内容でそれもちょっと読んでみたり……。

余談だけど Cloud9 すごかった。ついさっき教えられて 1~2 時間触っただけだけどなんかすごかった。リポジトリを fork してきてそれをブラウザ上でけっこうリッチな感じのエディタで開けてそれを共有するとカーソルやら選択範囲がシェアされて……、みたいな。

なぜ Rack か

Rack は Ruby を使ってる Web 開発者ならだれでもほぼ絶対お世話になっているであろうライブラリ。webrick なんかのサーバーアプリケーションと Ruby のコードをつないでくれるやつ。Ruby on RailsSinatra をはじめとした 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

Rubyで変数が定義されているか確認する -- ぺけみさお

defined? が分かったはいいが今度は SPEC_ARGV がわからなかった。リポジトリ全体で検索してみると、SPEC_ARGV はテストファイルでのみ使われていてテスト用に定義してある ARGV だということがわかった。

三項演算子なので SPEC_ARGV が定義されていれば argv = SPEC_ARGV、定義されていなければ argv = ARGV ということになる。テスト環境以外ではコマンドライン引数の配列 ARGV が代入されるようだ。

で、@use_default_optionstrue が入る。

次は @optionsparse_options(argv) の戻り値を代入する。argv の内容は先ほどでも述べたとおりコマンドライン実行だと ARGV だ。では parse_options() のコードを見ていこう。