sinatra源码解读

rails sinatra 等一些ruby的web框架,都是属于rack app, 建立在rack基础上的。
rack是什么?官网上的解释是Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks.我的理解就是链接服务器和ruby应用框架的,它解析http,rack里有两个比较重要的class, Rack::Request, Rack::Response,Rack::Request 负责解析request, Rack::Response负责http的response,想了解具体rack的应用查看http://www.rubydoc.info/github/rack/rack/Rack/Mime。
rack可以use多个middleware和run一个app, middleware一般如下格式:

class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    puts 'mymiddleware'
    puts env.inspect
    if env['PATH_INFO'] == '/'
      @app.call(env)
    else
      [404, {'Content-Type' => 'text/plain'}, ['not ok']]
    end
  endend

run的那个app要响应call,可以如下:

my_app = proc do |env|
  [200, {'Content-Type' => 'text/plain'}, ["ok"]]end

那么就可以用rack在config.ru中这样写服务,如下:

use MyOtherrun my_app

以上只是展示了rack的感念,以及rack写服务的基础。因为sinatra是建立在rack基础之上的,所有这些应该算是读sinatra源码的前提准备。
1.sinatra的整体结构
那么,我们来看一下,sinatra是怎样来应对rack的要求的,sinatra大体结构是这样的,代码如下:

module Sinatra
  class Base

    # 用于作为middleware用
    def initialize(app=nil)
      super
      @app = app
      yield self if block_given?
    end

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

    def call!(env)
    end

    class << self

      # 用于作为app用
      def call(env)
        prototype.call(env)
      end

      def prototype
        @prototype ||= new
      end

    end
  endend

以上的代码结构就是sinatra应对rack的要求而组织的,使得sinatra既可以作为middleware又可以作为app,当然,sinatra要多一些magic,后面再说,如果只是这样的结构,sinatra程序就只能写成modular-style structure,不能写成classic style 的structure,对于modular-style structure 我写了一seed https://github.com/wpzero/sinatra_seed.gitclone下来看一下。,可以
下面我们来看,sinatra路径magic get/post/put/delete
2.sinatra 路径 class macro
sinatra 应用的代码如下:

require "sinatra/base"class UserController < Sinatra::Base
  get '/hello' do
    'hello'
  endend

这个get方法就是sinatra的一些magic,用class macro实现的。
class macro, 涉及到一些meta programming的概念,我这里稍微介绍一下
ruby 为什么有singleton method?
例如:

class C < Dendc1 = C.newc2 = C.newdef c1.hello
  puts 'hello'endc1.helloc2.hello

#=> 'hello'
#=> undefined method `hello' for #<C:0x007fe5442665c0>
这是为什么?c1和c2不是属于一同一个class吗?c1 look up method一定也是向右一步找到它的class(C),然后一路向上找class C的ancestors, 找是否有hello这个instance_method没,有就调用,可是c2为什么不能找到hello这个method,原因是ruby,在instance和它的class之间还有一层class(也就是大家所说的eigenclass),每一个 instance和它的eigenclass是唯一对应的。这就是hello这个方法的悉身之所,而instance c2的eigenclass与c1的eigenclass不同,所以c2不可找到hello。
有了eigenclass这个概念,我们就可以利用往eigenclass中加入 instance method,那么相应的instance就可以调用了。而class也是一个instance 只不过它的class是Class而已。
具体关系如图:

sinatra源码解读_第1张图片


那么我们就来看一个最简单的class macro。

class C
  class << self
    def macro
      puts 'macro'
    end
  end

  macroend

#=> macro

在class C 的eigenclass中定义一个方法,那么class C就可以调用。
那么sinatra是怎么实现get/put这些routes 的方法的?
大体结构如下:

class Base
  class << self

    def get
      #具体内容
    end

    def post
      #具体内容
    end

  endend

这样我们就可以调用get等方法在Base的subclass中。
下面我们具体分析它的实现的代码。
3.sinatra get 的具体实现(unboundmethod)
先介绍一下unboudmethod。
一般instance method都是属于某个class,而调用它的是该class的instance,那么该在instance method中就可以access该instance的instance variable。那么我们可不可以把一个instance method 指定给一个instance?ruby是支持这个功能的,那就是bind。
先看一个例子:

class Square
  def area
    @side * @side
  end
  def initialize(side)
    @side = side
  endendarea_un = Square.instance_method(:area)s = Square.new(12)area = area_un.bind(s)area.call   #=> 144

instance_method Returns an UnboundMethod representing the given instance method in mod.
而这有什么用呢?我们就可以把一些method存起来,在某一时刻bind给一个instance来执行。
联想一下sinatra的magic get方法,其实,它就是实现了在url 的path_info符合某个路径时,执行传给get的block,而且这个block是bind到一个Application instance上的(也就是前边的举的例子的UserController的instance)。
那么来看一下sinatra的源码,如下:

class Base
   class << self

      def get(path, opts={}, &block)
        route('GET', path, opts, &block)
      end

      def route(verb, path, options={}, &block)
        signature = compile!(verb, path, block, options)
        (@routes[verb] ||= []) << signature
        signature
      end

      def compile!(verb, path, block, options = {})
        method_name             = "#{verb} #{path}"
        unbound_method          = generate_method(method_name, &block)
        # string to regular expression, and get the route keys
        pattern, keys           = compile path
        conditions, @conditions = @conditions, []

        [ pattern, keys, conditions, block.arity != 0 ?
            proc { |a,p| unbound_method.bind(a).call(*p) } :
            proc { |a,p| unbound_method.bind(a).call } ]
      end

      # path to regular expression and keys
      def compile(path)
        keys = []
        if path.respond_to? :to_str
          pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
          pattern.gsub!(/((:\w+)|\*)/) do |match|
            if match == "*"
              keys << 'splat'
              "(.*?)"
            else
              keys << $2[1..-1]
              "([^/?#]+)"
            end
          end
          [/^#{pattern}$/, keys]
        elsif path.respond_to?(:keys) && path.respond_to?(:match)
          [path, path.keys]
        elsif path.respond_to?(:names) && path.respond_to?(:match)
          [path, path.names]
        elsif path.respond_to? :match
          [path, keys]
        else
          raise TypeError, path
        end
      end

      # 产生Base 的unboundmethod
      def generate_method(method_name, &block)
        # the current class is Base, self is Base
        define_method(method_name, &block)
        method = instance_method method_name
        remove_method method_name
        method
      end

   endend

这里generate_method用于把一个block变为Base的unboundmethod用的就是define_method加上instance_method。


你可能感兴趣的:(sinatra源码解读)