Sinatra
Sinatra
Sinatra 是ruby中最为简单的server框架,提供了一系列的dsl,来供构建server使用
目录结构概览
从目录结构看起, base.rb 中最为重要代码行数最多, 其中涵盖了所有的Sinatra重要代码, Response, Request, CommonLogger, NotFound, Helpers, Templates, Base
从调用逻辑看起
调用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
require 'sinatra' get '/' do 'Hello world!1' end get '/car' do end get '/car/info/:id' do |id| p '/car/info/:id ----' body '/car/info' status 200 end get '/car/info' do body '/car/info' status 200 end # main.rb extend Sinatra::Delegator call
代码调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|- extend Sinatra::Delegator |- delegate [:get, :put ...] :register |- Delegator.delegate(Application) |- define_method :get do |- Application.send(get, *args, &block) |- Application < Base |- Base.get(path, opts, &block) |- route('GET', path, opts, &block) |- (@routes[verb] || []) << compile!('GET', path, block, options) |- call |- call!(env) |- @request = Request.new(env) |- @response = Response.new |- invoke { dispatch! } |- invoke { error_block!(response.status) } |- res = catch(:halt) { yield } |- body res |- route!("GET", '/car/info', options, &block) |- 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 |- return unless match = pattern.match(route) |- block ? block[self, values] : yield(self, values)第一个步骤, 将 :get, :put 等方法, 委托给Application, Application 继承Base,拿get举例, get 的调用在@routes中添加 将proc 取消binding的 可执行proc(不知道为什么需要这样,详细见 generate_method, 而且method_name 还可以是 “{ver} ${path}”), Regrex 对象等
第二个执行, rack 中中间件调用call, 构建了response, Request.new(env), invoke 为一个接住halt异常,并且记录返回数据到response 的函数, dispatch!则更为重要, 在其中执行了route!, route!的目的为执行路由,寻找匹配的路由,然后执行其中的可执行proc, process_route, 使用存在routes中的正则匹配路径是否命中,命中则执行对应的proc,
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
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 class Application < Base; end class Base def get(path, opts = {}, &block) conditions = @conditions.dup route('GET', path, opts, &block) @conditions = conditions route('HEAD', path, opts, &block) end def route(verb, path, options = {}, &block) # Because of self.options.host signature = compile!(verb, path, block, options) (@routes[verb] ||= []) << signature end def compile!(verb, path, block, options = {}) 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 } [ pattern, keys, conditions, wrapper ] end def generate_method(method_name, &block) method_name = method_name.to_sym define_method(method_name, &block) method = instance_method method_name remove_method method_name method end end def call!(env) # :nodoc: @env = env @request = Request.new(env) @response = Response.new @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 def invoke res = catch(:halt) { yield } res = [res] if Integer === res or String === res body(res) end 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 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 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
总结 1. DSL中通过将用户的block, 解除bind,执行bind来达到更换执行环境的问题,用这种方法实现的有 :get, :error 等, filter 2. sinatra提供的一下halt, pass 等,是采用throw :halt, 等来实现的, throw异常 然后中断正常处理,从而由sinatra接管 3. sinatra在路由匹配上每次都会将所有的现有的路由,首先通过verb(get, post, put) 等,过滤, 然后 循环匹配, 4. 关于渲染模板中如何将实例变量(@name=’xx’) 带入到模板中去, sinatra使用的为tilt, 在render的时候 将Sinatra::Application 实例传递进去, 使tilt在Application的实例变量中渲染模板(get 获得的代码块在Application实例中执行, 所以实例变量等都会副职在 其中, 而渲染模板的环境也在Application中,保证能够正常执行) 5. 这里在halt的实现方式上就有对比了, Rails vs Sinatra, rails中的callback实现的方式为每次检查Environment的变量, sinatra中为抛出异常, 传统认为抛出异常的代价是很高的。 6. sinatra中的invoke抽象的非常好, invoke是执行block然后将结果保存到body, status,response中 7. error 中的管理方法同路由中的一样,保存在类变量中, error => @error, :get, :put => @route, 8. 其中的Delegator的设定, 可以避免代码污染,污染全局空间,将委托方法放在一个module中