takoblo

コード読んだめも

sinatra

1ヶ月くらい前に7年ぶりのメジャーアップデートリリースがあった.rack2.0, ruby 2.2+ に対応したり rails5 との互換性が実装されたらしい.

コードリーディングは, sinatra の classic app の

  • rackup コマンドなしでサーバーが起動する部分
  • DSLの実装

を読んでいく.

Rack

Rackに関してここでは説明しないが前提知識としてあったほうがいい. k0kubunさんの記事がとてもわかりやすかったです.

http://qiita.com/k0kubun/items/248395f68164b52aec4a

server の起動

entry point

require './app'
run Sinatra::Application

これだけで app.rbDSLを書けば ruby app.rb コマンドでサーバが起動する.

Sinatra::Application をみると,at_exitApplication.run! をフックしてる

at_exit { Application.run! if $!.nil? && Application.run? }

Sinatra::Base#run!

Sinatra::ApplicationSinatra::Base のサブクラス. #detect_rack_handler で ひっぱってきたRackハンドラに対して #start_serverhandler.run してサーバを起動してる

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

DSLの実装

Rack::BuilderSinatra::Delegator を include してる

class Rack::Builder
  include Sinatra::Delegator
end

Sinatra::DelegatorDSLのメソッドを define_method で定義して,Sinatra::Applicationsend してる.

module Delegator #:nodoc:
  def self.delegate(*methods)
    methods.each do |method_name|
      define_method(method_name) do |*args, &block|
        return super(*args, &block) if respond_to? method_name
        Delegator.target.send(method_name, *args, &block)
      end
      private method_name
    end
  end

  delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
           :template, :layout, :before, :after, :error, :not_found, :configure,
           :set, :mime_type, :enable, :disable, :use, :development?, :test?,
           :production?, :helpers, :settings, :register

  class << self
    attr_accessor :target
  end

  self.target = Application
end

Rack application

main.rb で フックしてるのは Application.run なので,Sinatra::Application#call ではなく Sinatra::Application.call が呼ばれる(実装は両方ある)

def call(env)
  synchronize { prototype.call(env) }
end

prototypeにはrack middlewareでラップされたrack application が入ってるっぽいので結局 #call がよばれるみたいだけどちょっと自信ない.

def call!(env) # :nodoc:
  @env      = env
  @request  = Request.new(env)
  @response = Response.new
  template_cache.clear if settings.reload_templates

  @response['Content-Type'] = nil
  invoke { dispatch! }
  invoke { error_block!(response.status) } unless @env['sinatra.error']

  unless @response['Content-Type']
    if Array === body and body[0].respond_to? :content_type
      content_type body[0].content_type
    else
      content_type :html
    end
  end

  @response.finish
end

dispatch!#route! がよばれてその中でroutingしてるっぽいけどこの辺はあんまりちゃんとよんでない.

かんそう

rackに関してちょっと勉強になった. at_exit でフックしてるのはだいぶアクロバティックに見えるけどどうなんだろ.

とりあえず1984行の base.rb は迫力満点なのでファイル分割してほしい