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而已。
具体关系如图:
那么我们就来看一个最简单的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。