Sinatra How Sinatra Works

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

入口在哪里

最简单的 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 最难理解的地方应该就是这两个作用域了。

学习下。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 来了。

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