Sinatra::Base::run!を読んだ

勉強のためSinatra::Baseクラスの特異メソッド(クラス・メソッド)run!を読んだ。(バージョンは1.4.6)
Sinatra::BaseはModularスタイルでアプリケーションを記述していくときに利用するクラス。
Modularスタイルでは以下のようにアプリケーションを記述する。

require "sinatra/base"

class MyApp < Sinatra::Base
  get "/" do
    "hoge"
  end
end

MyApp.run!

run!メソッドの定義はsinatra/base.rbの1434行目から始まる。

def run!(options = {}, &block)
  return if running?
  set options
  handler            = detect_rack_handler
  handler_name  = handler.name.gsub(/.*::/, '')
  server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
  server_settings.merge!(:Port => port, :Host => bind)

  begin
    start_server(handler, server_settings, handler_name, &block)
  rescue Errno::EADDRINUSE
    $stderr.puts "== Someone is already performing on port #{port}!"
    raise
  ensure
    quit!
  end
end
  • 既にサーバーが起動していたら、何もせずに呼び出し元に戻る。
  • 起動していなかったら、各種初期化処理を行ってからstart_serverを呼び出しサーバーを起動している

以下、run!メソッド中に出てくるrunning?、set、detect_rack_handler、start_serverをみていく。

running?メソッド

running?の定義はsinatra/base.rbの1455行目で、running_server?メソッドを呼び出しているだけの単純なメソッド

def running?
  running_server?
end

running_server?メソッドはというと、run!メソッドの中にも出てきたsetメソッドの中で動的に定義されている。 どういうふうに定義されるかは後でみる。

setメソッド

setメソッドの定義はsinatra/base.rbの1205行目から始まる。setという名前にしては複雑なつくりになっている。

def set(option, value = (not_set = true), ignore_setter = false, &block)
  raise ArgumentError if block and !not_set
  value, not_set = block, false if block

  if not_set
    raise ArgumentError unless option.respond_to?(:each)
    option.each { |k,v| set(k, v) }
    return self
  end

  if respond_to?("#{option}=") and not ignore_setter
    return __send__("#{option}=", value)
  end

  setter = proc { |val| set option, val, true }
  getter = proc { value }

  case value
  when Proc
    getter = value
  when Symbol, Fixnum, FalseClass, TrueClass, NilClass
    getter = value.inspect
  when Hash
    setter = proc do |val|
      val = value.merge val if Hash === val
      set option, val, true
    end
  end

  define_singleton("#{option}=", setter) if setter
  define_singleton(option, getter) if getter
  define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
  self
end
  • 最初に引数の与え方が適切かどうかチェックしている。
  • 引数の種類によって、挙動が変わるようになっている。例えばハッシュのみを引数として与えた場合、ハッシュの各キー・値のペアに対してsetを再帰的に呼び出すようになっている。
  • セットされたを保持する方法が変わってる。クラスインスタンス変数などに保持するのではなく、Probオブジェクトのセッターとゲッターを作り、クロージャとして保持している。それが最後の3つのdefine_singletonの部分。

detect_rack_handlerメソッド

detect_rack_handlerの定義はsinatra/base.rbの1769行目から始まる。

def detect_rack_handler
  servers = Array(server)
  servers.each do |server_name|
    begin
      return Rack::Handler.get(server_name.to_s)
    rescue LoadError, NameError
    end
  end
  fail "Server handler (#{servers.join(',')}) not found."
end
  • serversの中から1つずつみていって、最初にRack::Handler.getが成功したものを返すようになっている。

Array(server)のserverに関係する部分は sinatra/base.rbの1865行目

set :server, %w[HTTP webrick]

と1871行目から1880行目

if ruby_engine == 'macruby'
  server.unshift 'control_tower'
else
  server.unshift 'reel'
  server.unshift 'mongrel'  if ruby_engine.nil?
  server.unshift 'puma'     if ruby_engine != 'rbx'
  server.unshift 'thin'     if ruby_engine != 'jruby'
  server.unshift 'puma'     if ruby_engine == 'rbx'
  server.unshift 'trinidad' if ruby_engine == 'jruby'
end

start_server

start_serverの定義はsinatra/base.rbの1504行目から。

def start_server(handler, server_settings, handler_name)
  handler.run(self, server_settings) do |server|
    unless handler_name =~ /cgi/i
      $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
    end

    setup_traps
    set :running_server, server
    set :handler_name,   handler_name
    server.threaded = settings.threaded if server.respond_to? :threaded=

      yield server if block_given?
  end
end
  • さっき設定したhandlerのrunメソッドを呼び出して、シグナルハンドラを設定している。

以上。

コードを読めばわかるようなことをだらだら解説していくだけになってしまった。
handlerの役割がいまいちわかってない。Rack::Handlerあたりを深追いしてみようか。