Sinatra How Sinatra Works

hooopo for Shopper+ · 2013年01月10日 · 最后由 robbin 回复于 2013年03月26日 · 7265 次阅读
本帖已被设为精华帖!

入口在哪里

最简单的sinatra程序是这样的:

# app.rb
require 'sinatra'

get '/' do
    'Hello world!'
end

只需要ruby app.rb就可以运行,居然不需要任何启动脚本。在哪里启动的app server?

# https://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25
at_exit { Application.run! if $!.nil? && Application.run? }

魔法就在上面那句代码里。上面这句代码是在app.rb里require "sinatra"的时候被执行的。 所以启动流程应该是这样的:

  1. 执行ruby app.rb
  2. app.rb里的两句代码被执行完毕程序退出
  3. 退出动作触发at_exit事件
  4. at_exit回调里内容被执行: Application.run!
  5. 找到Rack::Handler,然后启动服务器(https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1408-L1425

Sinatra::Application到底是什么

  1. Sinatra::Application是Rack App: Sinatra::Application本身就是一个合法的Rack App,可以被call,并且返回正确的格式。
  2. Sinatra::Application是Rack Middleware: 是合法的Rack App,并且可以initialize(app)
  3. Sinatra::Application是Rack Builder: 实现了usebuild接口,所以可以方便的使用use这样的DSL添加中间件来扩展。

理解了上面这些就自然明白了为什么可以被Rack::Handler run了。

经典风格

关于sinatra的两种风格,请看saito同学的这篇:http://ruby-china.org/topics/2110

# https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1752-L1773
module Sinatra
  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, :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
end

模块化风格很好理解,use/get/post这些方法是Base的类方法,下面写法一目了然:

require'sinatra/base'

class Hello < Sinatra::Base

  get "/" do
    "hello world"
  end

  run!
end

而经典模式的写法让人很难一眼看出get/post/use/run!/set这些方法是被定义在哪里的。关键一句在这里:https://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L30

extend Sinatra::Delegator

也就是说,经典风格是把DSL方法定义成Sinatra::Delegator的类方法,然后extend给顶层对象(top level /main object),在顶层作用域调用DSL方法(get/set/post/use)的时候其实调用的是Sinatra::Delegator的类方法,然后改变的是Delegator的target(Sinatra::Application).

经典风格其实是模块风格的一个特例。是一个只叫Application的模块,同时为了方便DSL,在顶层对象上面委托了一些常用方法。理解了经典风格,模块化风格自然就明白了。

Sinatra 扩展

和Rails里的插件机制不同,扩展Sinatra不能简单的用extend和include,而是对应的替换为register和helpers.不修改Base的好处是多个模块间不会相互影响,代价是写扩展难度大了一点儿,不那么直观。

简单的说,sinatra主要分两个作用域,一个叫DSL (class) Context,另一个叫Request Context。register和helpers分别扩展这两个作用域。详细的看这里吧:http://www.sinatrarb.com/extensions.html

好吧,我发现sinatra最难理解的地方应该就是这两个作用域了。

共收到 15 条回复

学习下。sinatra是个好东东。

没细看过sinatra

Good. Thanks a lot.

上面那段代码理解起来有些难度。

感谢LZ分享,

@metal 这本书很有用。 前段时间刚看完Rack,正想着去看看Sinatra,就看到这个帖子,来得早不如来得巧~

现在的趋势是模块化风格吗?

#8楼 @goinaction 看情况啦 ,个人推荐模块化风格,写起来差别不大,只是多写了一点默认设置。

经典风格有两个缺点:

  1. 一个进程内只能有一个classic style App
  2. 当你想把sinatra App当作gem/plugin发布的时候有必要做成modular style,如果不这样的话,多个sinatra App会引发冲突。

之前做一个简单的网站用了sinatra,有太多在Rails里很好用的gem都不适用,不知道现在好一点了没有

#10楼 @aquajach 用sinatra就要自己能折腾啊 如果想用现成的gem搭积木还是用Rails算了

#11楼 @hooopo 同意,所以sinatra还是比较适合功能性简单的项目,或者爱好纯折腾的人

用sinatra的好处:

  1. 自己搭积木,可以根据自己的需求选择,对项目控制力很强,出现问题好解决。
  2. 速度比Rails快很多,内存比Rails省很多
  3. threadsafe,用rainbows跑,IO并发处理能力有大幅度提高。尤其适合写移动应用的后端。

@hooopo 你boss来了。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册