来自1.4.7

requrie 'sinatra'将导致top扩展Sinatra::Delegator

extend Sinatra::Delegator


而Delegator是具备get、post、set等方法的,这些方法最终委派到Sinatra::Application

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


Sinatra::Application的get、post继承自Sinatra::Base,它们实际上是调用route来设置各种verb路由,且都是class method

def get(path, opts = {}, &block)
  conditions = @conditions.dup
  route('GET', path, opts, &block)

  @conditions = conditions
  route('HEAD', path, opts, &block)
end

def put(path, opts = {}, &bk)     route 'PUT',     path, opts, &bk end
def post(path, opts = {}, &bk)    route 'POST',    path, opts, &bk end
def delete(path, opts = {}, &bk)  route 'DELETE',  path, opts, &bk end
def head(path, opts = {}, &bk)    route 'HEAD',    path, opts, &bk end
def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
def patch(path, opts = {}, &bk)   route 'PATCH',   path, opts, &bk end
def link(path, opts = {}, &bk)    route 'LINK',    path, opts, &bk end
def unlink(path, opts = {}, &bk)  route 'UNLINK',  path, opts, &bk end


route方法主要是将path编译成“用于匹配url的正则表达式、匹配后与参数键值对应、选项、用于响应url的方法”的signature,然后将signature保存在@routes[verb]

def route(verb, path, options = {}, &block)
  # Because of self.options.host
  host_name(options.delete(:host)) if options.key?(:host)
  enable :empty_path_info if path == "" and empty_path_info.nil?
  signature = compile!(verb, path, block, options)
  (@routes[verb] ||= []) << signature
  invoke_hook(:route_added, verb, path, block)
  signature
end


将url转换成正则表达式的compile比较复杂,可以去看compile_test,看它到底支持哪些模式(现在的compile好像是用musterman来做了)

def compile!(verb, path, block, options = {})
  options.each_pair { |option, args| send(option, *args) }
  method_name             = "#{verb} #{path}"
  unbound_method          = generate_method(method_name, &block)
  pattern, keys           = compile path
  conditions, @conditions = @conditions, []

  wrapper                 = block.arity != 0 ?
    proc { |a,p| unbound_method.bind(a).call(*p) } :
    proc { |a,p| unbound_method.bind(a).call }
  wrapper.instance_variable_set(:@route_name, method_name)

  [ pattern, keys, conditions, wrapper ]
end


(unbound_method这种做法解释如下:In fact, earlier versions of Sinatra do use  instance_eval . However, there is an alternative: dynamically create a method from that block, get the unbound method object for that method, and remove the method immediately. When you want to run the code, bind the method object to the current instance  and call it.  This has a few advantages over  instance_eval : it results in significantly better performance since the scope change only occurs once as opposed to every request. It also allows  the passing of arguments to the block. Moreover, since you can name the method  yourself, it results in more readable stack traces. All of this logic is wrapped in Sinatra’s  generate_method)

搜索下源码,可发现routes是在route!里使用的。为得知请求时怎样到达那里的,在响应方法里打印调用栈,如

get '/' do
  puts caller
  "Bye, world!"
end


得到

/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1611:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1611:in `block in compile!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:975:in `[]'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:975:in `block (3 levels) in route!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:994:in `route_eval'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:975:in `block (2 levels) in route!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1015:in `block in process_route'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1013:in `catch'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1013:in `process_route'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:973:in `block in route!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:972:in `each'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:972:in `route!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1085:in `block in dispatch!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1067:in `block in invoke'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1067:in `catch'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1067:in `invoke'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1082:in `dispatch!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:907:in `block in call!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1067:in `block in invoke'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1067:in `catch'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:1067:in `invoke'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:907:in `call!'
/home/ken/.rvm/gems/ruby-2.2.2/gems/sinatra-1.4.7/lib/sinatra/base.rb:895:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb:18:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb:16:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb:18:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb:31:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-1.6.4/lib/rack/logger.rb:15:in `call'
/home/ken/.rvm/gems/ruby-2.2.2/gems/rack-1.6.4/lib/rack/commonlogger.rb:33:in `call'
......


往上查找,可发现从call开始(确实也符合rack标准)。因处理流程切割成多个方法,需要以实例变量来保持状态,所以dup

def call(env)
  dup.call!(env)
end

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

  @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


一路到invoke

def invoke
  res = catch(:halt) { yield }
  res = [res] if Fixnum === res or String === res
  if Array === res and Fixnum === res.first
    res = res.dup
    status(res.shift)
    body(res.pop)
    headers(*res)
  elsif res.respond_to? :each
    body res
  end
  nil # avoid double setting the same response tuple twice
end


invoke执行包裹成block的dispatch!,而dispatch!又invoke了route!

def dispatch!
  invoke do
    static! if settings.static? && (request.get? || request.head?)
    filter! :before
    route!
  end
rescue ::Exception => boom
  invoke { handle_exception!(boom) }
ensure
  begin
    filter! :after unless env['sinatra.static_file']
  rescue ::Exception => boom
    invoke { handle_exception!(boom) } unless @env['sinatra.error']
  end
end


终于到了route!

def route!(base = settings, pass_block = nil)
  if routes = base.routes[@request.request_method]
    routes.each do |pattern, keys, conditions, block|
      returned_pass_block = process_route(pattern, keys, conditions) do |*args|
        env['sinatra.route'] = block.instance_variable_get(:@route_name)
        route_eval { block[*args] }
      end

      # don't wipe out pass_block in superclass
      pass_block = returned_pass_block if returned_pass_block
    end
  end

  # Run routes defined in superclass.
  if base.superclass.respond_to?(:routes)
    return route!(base.superclass, pass_block)
  end

  route_eval(&pass_block) if pass_block
  route_missing
end


先找出verb所对应的响应方法集,再循环地process_route(注意,routes的排序按脚本中定义的顺序,The First Sufficient Match Wins)

def process_route(pattern, keys, conditions, block = nil, values = [])
  route = @request.path_info
  route = '/' if route.empty? and not settings.empty_path_info?
  return unless match = pattern.match(route)
  values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }

  if values.any?
    original, @params = params, params.merge('splat' => [], 'captures' => values)
    keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
  end

  catch(:pass) do
    conditions.each { |c| throw :pass if c.bind(self).call == false }
    block ? block[self, values] : yield(self, values)
  end
ensure
  @params = original if original
end


如果match,则运行刚才调用process_route 时所带的block,内含route_eval。因之前已将响应方法包装成{ |a,p| unbound_method.bind(a).call },所以yield(self, values)使响应方法能以params访问到dup出来的Base的@params

而route_eval是执行响应方法块并以throw :halt来返回响应方法块的返回值的

def route_eval
  throw :halt, yield
end


于是,便跳出route!,让dipatch!的invoke能拦截:halt返回值并设置response

再回到call!,最终,@response.finish,以符合rack规范的方式返回[status, header, body],完成响应。