入门

Gem Version Testing

Sinatra 是一个用于在 Ruby 中快速创建 Web 应用程序的 DSL,只需最少的努力。

# myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

安装所需的 gem

gem install sinatra
gem install rackup
gem install puma # or any other server (optional step)

并使用以下命令运行

ruby myapp.rb

查看地址:http://localhost:4567

您更改的代码只有在您重新启动服务器后才会生效。每次更改代码或使用代码重新加载器(如 rerunrack-unreloader)时,请重新启动服务器。

建议您也运行 gem install puma,Sinatra 会在可用时选择它。

路由

在 Sinatra 中,路由是 HTTP 方法与 URL 匹配模式的配对。每个路由都与一个代码块相关联。

get '/' do
  .. show something ..
end

post '/' do
  .. create something ..
end

put '/' do
  .. replace something ..
end

patch '/' do
  .. modify something ..
end

delete '/' do
  .. annihilate something ..
end

options '/' do
  .. appease something ..
end

link '/' do
  .. affiliate something ..
end

unlink '/' do
  .. separate something ..
end

路由按定义顺序匹配。第一个匹配请求的路由将被调用。

带有尾部斜杠的路由与没有尾部斜杠的路由不同。

get '/foo' do
  # Does not match "GET /foo/"
end

路由模式可能包含命名参数,可以通过 params 哈希访问。

get '/hello/:name' do
  # matches "GET /hello/foo" and "GET /hello/bar"
  # params['name'] is 'foo' or 'bar'
  "Hello #{params['name']}!"
end

您也可以通过代码块参数访问命名参数。

get '/hello/:name' do |n|
  # matches "GET /hello/foo" and "GET /hello/bar"
  # params['name'] is 'foo' or 'bar'
  # n stores params['name']
  "Hello #{n}!"
end

路由模式也可能包含 splat(或通配符)参数,可以通过 params['splat'] 数组访问。

get '/say/*/to/*' do
  # matches /say/hello/to/world
  params['splat'] # => ["hello", "world"]
end

get '/download/*.*' do
  # matches /download/path/to/file.xml
  params['splat'] # => ["path/to/file", "xml"]
end

或使用代码块参数。

get '/download/*.*' do |path, ext|
  [path, ext] # => ["path/to/file", "xml"]
end

使用正则表达式进行路由匹配

get /\/hello\/([\w]+)/ do
  "Hello, #{params['captures'].first}!"
end

或使用代码块参数。

get %r{/hello/([\w]+)} do |c|
  # Matches "GET /meta/hello/world", "GET /hello/world/1234" etc.
  "Hello, #{c}!"
end

路由模式可能包含可选参数。

get '/posts/:format?' do
  # matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc
end

路由也可以使用查询参数。

get '/posts' do
  # matches "GET /posts?title=foo&author=bar"
  title = params['title']
  author = params['author']
  # uses title and author variables; query is optional to the /posts route
end

顺便说一句,除非您禁用路径遍历攻击保护(请参阅 下方),否则请求路径可能会在与您的路由匹配之前被修改。

您可以通过传入一个 :mustermann_opts 哈希来自定义用于特定路由的 Mustermann 选项。

get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
  # matches /posts exactly, with explicit anchoring
  "If you match an anchored pattern clap your hands!"
end

它看起来像一个 条件,但它不是!这些选项将被合并到 下方 描述的全局 :mustermann_opts 哈希中。

条件

路由可能包含各种匹配条件,例如用户代理。

get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
  "You're using Songbird version #{params['agent'][0]}"
end

get '/foo' do
  # Matches non-songbird browsers
end

其他可用的条件是 host_nameprovides

get '/', :host_name => /^admin\./ do
  "Admin Area, Access denied!"
end

get '/', :provides => 'html' do
  haml :index
end

get '/', :provides => ['rss', 'atom', 'xml'] do
  builder :feed
end

provides 搜索请求的 Accept 标头。

您可以轻松地定义自己的条件。

set(:probability) { |value| condition { rand <= value } }

get '/win_a_car', :probability => 0.1 do
  "You won!"
end

get '/win_a_car' do
  "Sorry, you lost."
end

对于接受多个值的条件,请使用 splat。

set(:auth) do |*roles|   # <- notice the splat here
  condition do
    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      redirect "/login/", 303
    end
  end
end

get "/my/account/", :auth => [:user, :admin] do
  "Your Account Details"
end

get "/only/admin/", :auth => :admin do
  "Only admins are allowed here!"
end

返回值

路由代码块的返回值至少决定传递给 HTTP 客户端的响应主体,或者至少决定 Rack 堆栈中的下一个中间件。最常见的是,这是一个字符串,如上面的示例所示。但其他值也被接受。

您可以返回一个对象,该对象可以是有效的 Rack 响应、Rack 主体对象或 HTTP 状态代码。

  • 包含三个元素的数组:[状态 (整数),头 (哈希),响应主体 (响应 #each)]
  • 包含两个元素的数组:[状态 (整数),响应主体 (响应 #each)]
  • 响应 #each 的对象,并且只将字符串传递给给定的块。
  • 表示状态代码的整数。

这样,我们可以轻松地实现一个流式示例。

class Stream
  def each
    100.times { |i| yield "#{i}\n" }
  end
end

get('/') { Stream.new }

您也可以使用 stream 辅助方法(见下文)来减少样板代码并将流式逻辑嵌入到路由中。

自定义路由匹配器

如上所示,Sinatra 内置支持使用字符串模式和正则表达式作为路由匹配器。但是,它并没有止步于此。您可以轻松地定义自己的匹配器。

class AllButPattern
  def initialize(except)
    @except = except
  end

  def to_pattern(options)
    return self
  end

  def params(route)
    return {} unless @except === route
  end
end

def all_but(pattern)
  AllButPattern.new(pattern)
end

get all_but("/index") do
  # ...
end

请注意,上面的示例可能过于复杂,因为它也可以表示为

get /.*/ do
  pass if request.path_info == "/index"
  # ...
end

静态文件

静态文件从 ./public 目录提供服务。您可以通过设置 :public_folder 选项来指定不同的位置。

set :public_folder, __dir__ + '/static'

请注意,公共目录名称不包含在 URL 中。文件 ./public/css/style.css 可作为 http://example.com/css/style.css 使用。

使用 :static_cache_control 设置(见 下文)来添加 Cache-Control 头信息。

视图/模板

每种模板语言都通过其自己的渲染方法公开。这些方法只返回一个字符串。

get '/' do
  erb :index
end

这将渲染 views/index.erb

除了模板名称,您也可以直接传入模板内容。

get '/' do
  code = "<%= Time.now %>"
  erb code
end

模板接受第二个参数,即选项哈希。

get '/' do
  erb :index, :layout => :post
end

这将渲染嵌入在 views/post.erb 中的 views/index.erb(默认情况下是 views/layout.erb,如果存在)。

任何 Sinatra 不理解的选项都将传递给模板引擎。

get '/' do
  haml :index, :format => :html5
end

您也可以为每种模板语言设置通用选项。

set :haml, :format => :html5

get '/' do
  haml :index
end

传递给渲染方法的选项会覆盖通过 set 设置的选项。

可用选项

locals
传递给文档的本地变量列表。对于部分内容很有用。示例:erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
如果无法确定,要使用的字符串编码。默认值为 settings.default_encoding
视图
加载模板的视图文件夹。默认值为 settings.views
布局
是否使用布局 (truefalse)。如果它是一个符号,则指定要使用的模板。例如:erb :index, :layout => !request.xhr?
内容类型
模板生成的 Content-Type。默认值取决于模板语言。
范围
渲染模板的范围。默认值为应用程序实例。如果更改此设置,则实例变量和辅助方法将不可用。
布局引擎
用于渲染布局的模板引擎。对于不支持布局的其他语言很有用。默认值为用于模板的引擎。例如:set :rdoc, :layout_engine => :erb
布局选项
仅用于渲染布局的特殊选项。例如:set :rdoc, :layout_options => { :views => 'views/layouts' }

假设模板位于 ./views 目录下。要使用不同的视图目录

set :views, settings.root + '/templates'

要记住的一件重要的事情是,您始终必须使用符号引用模板,即使它们位于子目录中(在这种情况下,请使用::'subdir/template''subdir/template'.to_sym)。您必须使用符号,否则渲染方法将直接渲染传递给它们的所有字符串。

文字模板

get '/' do
  haml '%div.title Hello World'
end

渲染模板字符串。您可以选择指定 :path:line 以获得更清晰的回溯,如果存在与该字符串关联的文件系统路径或行。

get '/' do
  haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
end

可用的模板语言

某些语言有多种实现。要指定要使用的实现(并使其线程安全),您只需先要求它即可

require 'rdiscount'
get('/') { markdown :index }

Haml 模板

依赖项 haml
文件扩展名 .haml
示例 haml :index, :format => :html5

Erb 模板

依赖项 erubi 或 erb(包含在 Ruby 中)
文件扩展名 .erb.rhtml.erubi(仅限 Erubi)
示例 erb :index

Builder 模板

依赖项 builder
文件扩展名 .builder
示例 builder { |xml| xml.em "hi" }

它还接受一个块用于内联模板(请参阅 示例)。

Nokogiri 模板

依赖项 nokogiri
文件扩展名 .nokogiri
示例 nokogiri { |xml| xml.em "hi" }

它还接受一个块用于内联模板(请参阅 示例)。

Sass 模板

依赖项 sass-嵌入式
文件扩展名 .sass
示例 sass :stylesheet, :style => :expanded

Scss 模板

依赖项 sass-嵌入式
文件扩展名 .scss
示例 scss :stylesheet, :style => :expanded

Liquid 模板

依赖项 liquid
文件扩展名 .liquid
示例 liquid :index, :locals => { :key => 'value' }

由于您无法从 Liquid 模板中调用 Ruby 方法(除了 yield),因此您几乎总是希望将局部变量传递给它。

Markdown 模板

依赖项 以下任一:RDiscountRedCarpetkramdowncommonmarker pandoc
文件扩展名 .markdown.mkd.md
示例 markdown :index, :layout_engine => :erb

无法从 Markdown 中调用方法,也无法向其传递局部变量。因此,您通常会将其与另一个渲染引擎结合使用。

erb :overview, :locals => { :text => markdown(:introduction) }

请注意,您也可以从其他模板中调用 markdown 方法。

%h1 Hello From Haml!
%p= markdown(:greetings)

由于您无法从 Markdown 中调用 Ruby,因此您无法使用用 Markdown 编写的布局。但是,可以通过传递 :layout_engine 选项,为模板使用与布局不同的渲染引擎。

RDoc 模板

依赖项 RDoc
文件扩展名 .rdoc
示例 rdoc :README, :layout_engine => :erb

无法从 RDoc 中调用方法,也无法向其传递局部变量。因此,您通常会将其与另一个渲染引擎结合使用。

erb :overview, :locals => { :text => rdoc(:introduction) }

请注意,您也可以从其他模板中调用 rdoc 方法。

%h1 Hello From Haml!
%p= rdoc(:greetings)

由于您无法从 RDoc 中调用 Ruby,因此您无法使用用 RDoc 编写的布局。但是,可以通过传递 :layout_engine 选项,为模板使用与布局不同的渲染引擎。

AsciiDoc 模板

依赖项 Asciidoctor
文件扩展名 .asciidoc.adoc.ad
示例 asciidoc :README, :layout_engine => :erb

由于您无法直接从 AsciiDoc 模板中调用 Ruby 方法,因此您几乎总是希望将局部变量传递给它。

Markaby 模板

依赖项 Markaby
文件扩展名 .mab
示例 markaby { h1 "Welcome!" }

它还接受一个块用于内联模板(请参阅 示例)。

RABL 模板

依赖项 Rabl
文件扩展名 .rabl
示例 rabl :index

Slim 模板

依赖项 Slim 语言
文件扩展名 .slim
示例 slim :index

Yajl 模板

依赖项 yajl-ruby
文件扩展名 .yajl
示例 yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'

模板源代码被评估为 Ruby 字符串,生成的 json 变量使用 #to_json 转换。

json = { :foo => 'bar' }
json[:baz] = key

:callback:variable 选项可用于装饰渲染后的对象。

var resource = {"foo":"bar","baz":"qux"};
present(resource);

在模板中访问变量

模板在与路由处理程序相同的上下文中进行评估。在路由处理程序中设置的实例变量可以直接被模板访问。

get '/:id' do
  @foo = Foo.find(params['id'])
  haml '%h1= @foo.name'
end

或者,指定一个显式的局部变量哈希。

get '/:id' do
  foo = Foo.find(params['id'])
  haml '%h1= bar.name', :locals => { :bar => foo }
end

这通常用于在其他模板中渲染模板作为部分。

带有 yield 和嵌套布局的模板

布局通常只是一个调用yield的模板。这种模板可以通过上面描述的:template选项使用,也可以通过以下方式使用块渲染。

erb :post, :layout => false do
  erb :index
end

这段代码与erb :index, :layout => :post基本等效。

将块传递给渲染方法对于创建嵌套布局最为有用。

erb :main_layout, :layout => false do
  erb :admin_layout do
    erb :user
  end
end

这也可以用更少的代码行来完成。

erb :admin_layout, :layout => :main_layout do
  erb :user
end

目前,以下渲染方法接受块:erbhamlliquidslim。此外,通用render方法也接受块。

内联模板

模板可以在源文件末尾定义。

require 'sinatra'

get '/' do
  haml :index
end

__END__

@@ layout
%html
  != yield

@@ index
%div.title Hello world.

注意:在需要 Sinatra 的源文件中定义的内联模板会自动加载。如果您在其他源文件中使用内联模板,请显式调用enable :inline_templates

命名模板

模板也可以使用顶层template方法定义。

template :layout do
  "%html\n  =yield\n"
end

template :index do
  '%div.title Hello World!'
end

get '/' do
  haml :index
end

如果存在名为“layout”的模板,则每次渲染模板时都会使用它。您可以通过传递:layout => false来单独禁用布局,或者通过set :haml, :layout => false来默认禁用布局。

get '/' do
  haml :index, :layout => !request.xhr?
end

关联文件扩展名

要将文件扩展名与模板引擎关联,请使用Tilt.register。例如,如果您想使用tt文件扩展名来表示 Haml 模板,您可以执行以下操作。

Tilt.register Tilt[:haml], :tt

添加您自己的模板引擎

首先,使用 Tilt 注册您的引擎,然后创建一个渲染方法。

Tilt.register MyAwesomeTemplateEngine, :myat

helpers do
  def myat(*args) render(:myat, *args) end
end

get '/' do
  myat :index
end

渲染./views/index.myat。了解更多关于Tilt的信息。

使用自定义逻辑进行模板查找

要实现您自己的模板查找机制,您可以编写您自己的#find_template方法。

configure do
  set :views, [ './views/a', './views/b' ]
end

def find_template(views, name, engine, &block)
  Array(views).each do |v|
    super(v, name, engine, &block)
  end
end

过滤器

在过滤器在与路由相同的上下文中评估每个请求之前,可以修改请求和响应。在过滤器中设置的实例变量可以被路由和模板访问。

before do
  @note = 'Hi!'
  request.path_info = '/foo/bar/baz'
end

get '/foo/*' do
  @note #=> 'Hi!'
  params['splat'] #=> 'bar/baz'
end

在过滤器在与路由相同的上下文中评估每个请求之后,也可以修改请求和响应。在之前过滤器和路由中设置的实例变量可以被之后过滤器访问。

after do
  puts response.status
end

注意:除非您使用body方法而不是仅仅从路由返回字符串,否则在之后过滤器中将无法获得主体,因为它是在之后生成的。

过滤器可以选择性地接受一个模式,使其仅在请求路径与该模式匹配时才被评估。

before '/protected/*' do
  authenticate!
end

after '/create/:slug' do |slug|
  session[:last_slug] = slug
end

与路由类似,过滤器也接受条件。

before :agent => /Songbird/ do
  # ...
end

after '/blog/*', :host_name => 'example.com' do
  # ...
end

助手

使用顶层helpers方法定义辅助方法,供路由处理程序和模板使用

helpers do
  def bar(name)
    "#{name}bar"
  end
end

get '/:name' do
  bar(params['name'])
end

或者,辅助方法可以在模块中单独定义

module FooUtils
  def foo(name) "#{name}foo" end
end

module BarUtils
  def bar(name) "#{name}bar" end
end

helpers FooUtils, BarUtils

效果与在应用程序类中包含模块相同。

使用会话

会话用于在请求期间保持状态。如果激活,每个用户会话将拥有一个会话哈希

enable :sessions

get '/' do
  "value = " << session[:value].inspect
end

get '/:value' do
  session['value'] = params['value']
end

会话密钥安全性

为了提高安全性,cookie 中的会话数据使用HMAC-SHA1用会话密钥签名。此会话密钥最优地应为适当长度的加密安全随机值,对于HMAC-SHA1,该长度大于或等于 64 字节(512 位,128 个十六进制字符)。建议不要使用小于 32 字节随机数(256 位,64 个十六进制字符)的密钥。因此,**非常重要**的是不要随意设置密钥,而是使用安全的随机数生成器来创建它。人类在生成随机值方面非常糟糕。

默认情况下,Sinatra 会为您生成一个 32 字节的安全随机会话密钥,但它会在每次重新启动应用程序时更改。如果您有多个应用程序实例,并且让 Sinatra 生成密钥,那么每个实例将拥有不同的会话密钥,这可能不是您想要的。

为了提高安全性,建议您生成一个安全的随机密钥并将其存储在运行应用程序的每个主机上的环境变量中,以便所有应用程序实例共享同一个密钥。您应该定期将此会话密钥轮换到一个新值。以下是一些关于如何创建 64 字节密钥并设置它的示例

会话密钥生成

$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac

会话密钥环境变量

为 Sinatra 设置一个SESSION_SECRET环境变量,使其值为您生成的密钥。使此值在主机重启后保持持久。由于执行此操作的方法因系统而异,因此仅供说明。

# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc

会话密钥应用程序配置

设置您的应用程序配置,如果SESSION_SECRET环境变量不可用,则安全地回退到一个安全的随机密钥

require 'securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }

会话配置

如果您想进一步配置它,您也可以在sessions设置中存储一个包含选项的哈希值。

set :sessions, :domain => 'foo.com'

要将您的会话共享到 foo.com 子域上的其他应用程序,请在域名前面加上一个.,例如:

set :sessions, :domain => '.foo.com'

选择您自己的会话中间件

请注意,enable :sessions实际上将所有数据存储在 cookie 中。这可能并不总是您想要的(例如,存储大量数据会增加您的流量)。您可以使用任何 Rack 会话中间件来做到这一点,可以使用以下方法之一。

enable :sessions
set :session_store, Rack::Session::Pool

或者使用包含选项的哈希值设置会话。

set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool

另一个选择是调用enable :sessions,而是像使用其他中间件一样引入您选择的中间件。

重要的是要注意,使用此方法时,基于会话的保护默认情况下不会启用

还需要添加执行此操作的 Rack 中间件。

use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking

有关更多信息,请参阅“配置攻击保护”。

停止

要在过滤器或路由中立即停止请求,请使用

halt

您也可以在停止时指定状态

halt 410

或主体

halt 'this will be the body'

或两者

halt 401, 'go away!'

带有标题

halt 402, {'Content-Type' => 'text/plain'}, 'revenge'

当然可以将模板与halt结合使用。

halt erb(:error)

传递

路由可以使用pass将处理传递给下一个匹配的路由。

get '/guess/:who' do
  pass unless params['who'] == 'Frank'
  'You got me!'
end

get '/guess/*' do
  'You missed!'
end

路由块立即退出,控制权继续传递给下一个匹配的路由。如果找不到匹配的路由,则返回 404。

触发另一个路由

有时pass不是您想要的,相反,您希望获得调用另一个路由的结果。只需使用call即可实现这一点。

get '/foo' do
  status, headers, body = call env.merge("PATH_INFO" => '/bar')
  [status, headers, body.map(&:upcase)]
end

get '/bar' do
  "bar"
end

请注意,在上面的示例中,您可以通过简单地将"bar"移动到/foo/bar都使用的帮助程序中来简化测试并提高性能。

如果您希望请求发送到同一个应用程序实例而不是副本,请使用call!而不是call

如果您想了解更多关于call的信息,请查看 Rack 规范。

设置主体、状态代码和标题

可以使用路由块的返回值来设置状态代码和响应主体,这是一种可行且推荐的做法。但是,在某些情况下,您可能希望在执行流程中的任意点设置主体。您可以使用body帮助程序方法来做到这一点。如果您这样做,您可以从那时起使用该方法来访问主体。

get '/foo' do
  body "bar"
end

after do
  puts body
end

也可以将一个代码块传递给body,该代码块将由 Rack 处理程序执行(这可用于实现流式传输,参见“返回值”)。

与 body 类似,您还可以设置状态代码和标头

get '/foo' do
  status 418
  headers \
    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
    "Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt"
  body "I'm a teapot!"
end

body类似,不带参数的headersstatus可用于访问其当前值。

流式响应

有时您希望在生成响应主体的一部分时开始发送数据。在极端情况下,您希望不断发送数据,直到客户端关闭连接。您可以使用stream辅助函数来避免创建自己的包装器

get '/' do
  stream do |out|
    out << "It's gonna be legen -\n"
    sleep 0.5
    out << " (wait for it) \n"
    sleep 1
    out << "- dary!\n"
  end
end

这允许您实现流式 API、服务器发送事件,并且可以用作WebSocket的基础。如果某些内容(但并非所有内容)依赖于缓慢的资源,它也可以用于提高吞吐量。

请注意,流式行为(尤其是并发请求的数量)高度依赖于用于提供应用程序的 Web 服务器。某些服务器甚至可能根本不支持流式传输。如果服务器不支持流式传输,则在传递给stream的代码块执行完毕后,将一次性发送整个主体。流式传输在 Shotgun 中根本不起作用。

如果将可选参数设置为keep_open,它将不会在流对象上调用close,允许您在执行流程中的任何时间点关闭它。

您可以查看聊天示例

客户端在尝试写入套接字时也可能关闭连接。因此,建议在尝试写入之前检查out.closed?

日志记录

在请求范围内,logger辅助函数公开了一个Logger实例

get '/' do
  logger.info "loading data"
  # ...
end

此记录器将自动考虑您的 Rack 处理程序的记录设置。如果禁用记录,此方法将返回一个虚拟对象,因此您无需在路由和过滤器中担心它。

请注意,默认情况下,仅为Sinatra::Application启用记录,因此如果您从Sinatra::Base继承,您可能希望自己启用它

class MyApp < Sinatra::Base
  configure :production, :development do
    enable :logging
  end
end

要避免设置任何日志记录中间件,请将logging选项设置为nil。但是,请记住,在这种情况下,logger将返回nil。一个常见的用例是您想设置自己的日志记录器。Sinatra 将使用它在env['rack.logger']中找到的任何内容。

MIME 类型

当使用send_file或静态文件时,您可能遇到 Sinatra 不理解的 MIME 类型。使用mime_type通过文件扩展名注册它们。

configure do
  mime_type :foo, 'text/foo'
end

您也可以将其与content_type辅助方法一起使用。

get '/' do
  content_type :foo
  "foo foo foo"
end

生成 URL

要生成 URL,您应该使用url辅助方法,例如,在 Haml 中。

%a{:href => url('/foo')} foo

它会考虑反向代理和 Rack 路由器 - 如果存在。

此方法也被别名为to(有关示例,请参见下方)。

浏览器重定向

您可以使用redirect辅助方法触发浏览器重定向。

get '/foo' do
  redirect to('/bar')
end

任何其他参数都将像传递给halt的参数一样处理。

redirect to('/bar'), 303
redirect 'http://www.google.com/', 'wrong place, buddy'

您也可以轻松地重定向回用户来自的页面,方法是使用redirect back

get '/foo' do
  "<a href='/bar'>do something</a>"
end

get '/bar' do
  do_something
  redirect back
end

要使用重定向传递参数,请将其添加到查询中。

redirect to('/bar?sum=42')

或使用会话。

enable :sessions

get '/foo' do
  session[:secret] = 'foo'
  redirect to('/bar')
end

get '/bar' do
  session[:secret]
end

缓存控制

正确设置您的标头是实现正确 HTTP 缓存的基础。

您可以像这样轻松地设置 Cache-Control 标头。

get '/' do
  cache_control :public
  "cache it!"
end

专业提示:在 before 过滤器中设置缓存。

before do
  cache_control :public, :must_revalidate, :max_age => 60
end

如果您使用expires辅助方法来设置相应的标头,Cache-Control将自动为您设置。

before do
  expires 500, :public, :must_revalidate
end

要正确使用缓存,您应该考虑使用etaglast_modified。建议在执行任何繁重操作之前调用这些辅助方法,因为如果客户端在其缓存中已经拥有当前版本,它们将立即刷新响应。

get "/article/:id" do
  @article = Article.find params['id']
  last_modified @article.updated_at
  etag @article.sha1
  erb :article
end

也可以使用弱 ETag

etag @article.sha1, :weak

这些辅助方法不会为您执行任何缓存操作,而是将必要的信息提供给您的缓存。如果您正在寻找快速的反向代理缓存解决方案,请尝试使用rack-cache

require "rack/cache"
require "sinatra"

use Rack::Cache

get '/' do
  cache_control :public, :max_age => 36000
  sleep 5
  "hello"
end

使用:static_cache_control设置(参见下方)将Cache-Control标头信息添加到静态文件。

根据 RFC 2616,您的应用程序在设置 If-Match 或 If-None-Match 标头为*时,应该根据请求的资源是否存在而表现出不同的行为。Sinatra 假设安全(如 get)和幂等(如 put)请求的资源已经存在,而其他资源(例如 post 请求)被视为新资源。您可以通过传递:new_resource 选项来更改此行为。

get '/create' do
  etag '', :new_resource => true
  Article.create
  erb :new_article
end

如果您仍然想使用弱 ETag,请传递:kind 选项。

etag '', :new_resource => true, :kind => :weak

发送文件

要将文件的內容作为响应返回,您可以使用send_file 辅助方法。

get '/' do
  send_file 'foo.png'
end

它也接受选项。

send_file 'foo.png', :type => :jpg

选项是

filename
响应中使用的文件名,默认为真实文件名。
last_modified
Last-Modified 标头的值,默认为文件的 mtime。
type
Content-Type 标头的值,如果缺少,则从文件扩展名推断。
disposition
Content-Disposition 标头的值,可能的值:nil(默认)、:attachment:inline
length
Content-Length 标头的值,默认为文件大小。
status
要发送的状态代码。在将静态文件作为错误页面发送时很有用。如果 Rack 处理程序支持,将使用除从 Ruby 进程流式传输之外的其他方法。如果您使用此辅助方法,Sinatra 将自动处理范围请求。

访问请求对象

可以通过request 方法从请求级别(过滤器、路由、错误处理程序)访问传入的请求对象。

# app running on http://example.com/example
get '/foo' do
  t = %w[text/css text/html application/javascript]
  request.accept              # ['text/html', '*/*']
  request.accept? 'text/xml'  # true
  request.preferred_type(t)   # 'text/html'
  request.body                # request body sent by the client (see below)
  request.scheme              # "http"
  request.script_name         # "/example"
  request.path_info           # "/foo"
  request.port                # 80
  request.request_method      # "GET"
  request.query_string        # ""
  request.content_length      # length of request.body
  request.media_type          # media type of request.body
  request.host                # "example.com"
  request.get?                # true (similar methods for other verbs)
  request.form_data?          # false
  request["some_param"]       # value of some_param parameter. [] is a shortcut to the params hash.
  request.referrer            # the referrer of the client or '/'
  request.user_agent          # user agent (used by :agent condition)
  request.cookies             # hash of browser cookies
  request.xhr?                # is this an ajax request?
  request.url                 # "http://example.com/example/foo"
  request.path                # "/example/foo"
  request.ip                  # client IP address
  request.secure?             # false (would be true over ssl)
  request.forwarded?          # true (if running behind a reverse proxy)
  request.env                 # raw env hash handed in by Rack
end

一些选项,如script_namepath_info,也可以写入。

before { request.path_info = "/" }

get "/" do
  "all requests end up here"
end

request.body 是一个 IO 或 StringIO 对象。

post "/api" do
  request.body.rewind  # in case someone already read it
  data = JSON.parse request.body.read
  "Hello #{data['name']}!"
end

附件

您可以使用attachment 辅助方法告诉浏览器响应应该存储在磁盘上而不是在浏览器中显示。

get '/' do
  attachment
  "store it!"
end

您也可以传递文件名。

get '/' do
  attachment "info.txt"
  "store it!"
end

处理日期和时间

Sinatra 提供了一个time_for 辅助方法,它从给定值生成一个 Time 对象。它还能够转换DateTimeDate 和类似的类。

get '/' do
  pass if Time.now > time_for('Dec 23, 2016')
  "still time"
end

此方法在内部由expireslast_modified 等使用。因此,您可以通过在应用程序中覆盖time_for 来轻松扩展这些方法的行为。

helpers do
  def time_for(value)
    case value
    when :yesterday then Time.now - 24*60*60
    when :tomorrow  then Time.now + 24*60*60
    else super
    end
  end
end

get '/' do
  last_modified :yesterday
  expires :tomorrow
  "hello"
end

查找模板文件

find_template 辅助方法用于查找用于渲染的模板文件。

find_template settings.views, 'foo', Tilt[:haml] do |file|
  puts "could be #{file}"
end

这并没有什么实际用处。但你可以重写这个方法来接入你自己的查找机制,这很有用。例如,如果你想使用多个视图目录。

set :views, ['views', 'templates']

helpers do
  def find_template(views, name, engine, &block)
    Array(views).each { |v| super(v, name, engine, &block) }
  end
end

另一个例子是为不同的引擎使用不同的目录。

set :views, :haml => 'templates', :default => 'views'

helpers do
  def find_template(views, name, engine, &block)
    _, folder = views.detect { |k,v| engine == Tilt[k] }
    folder ||= views[:default]
    super(folder, name, engine, &block)
  end
end

你也可以轻松地将它封装成一个扩展并与他人分享!

请注意,find_template 不会检查文件是否存在,而是对所有可能的路径调用给定的块。这不是性能问题,因为 render 会在找到文件后立即使用 break。此外,如果你没有在开发模式下运行,模板位置(和内容)将被缓存。如果你写了一个非常疯狂的方法,你应该记住这一点。

配置

在任何环境中启动时运行一次。

configure do
  # setting one option
  set :option, 'value'

  # setting multiple options
  set :a => 1, :b => 2

  # same as `set :option, true`
  enable :option

  # same as `set :option, false`
  disable :option

  # you can also have dynamic settings with blocks
  set(:css_dir) { File.join(views, 'css') }
end

仅当环境(APP_ENV 环境变量)设置为 :production 时运行。

configure :production do
  ...
end

当环境设置为 :production:test 时运行。

configure :production, :test do
  ...
end

你可以通过 settings 访问这些选项。

configure do
  set :foo, 'bar'
end

get '/' do
  settings.foo? # => true
  settings.foo  # => 'bar'
  ...
end

配置攻击防护

Sinatra 使用 Rack::Protection 来保护你的应用程序免受常见的、机会主义的攻击。你可以轻松地禁用此行为(这将使你的应用程序容易受到大量常见漏洞的攻击)。

disable :protection

要跳过单个防御层,将 protection 设置为选项哈希。

set :protection, :except => :path_traversal

你也可以传入一个数组来禁用一组防护。

set :protection, :except => [:path_traversal, :remote_token]

默认情况下,Sinatra 只有在启用了 :sessions 时才会设置基于会话的防护。请参阅“使用会话”。有时你可能希望在 Sinatra 应用程序“外部”设置会话,例如在 config.ru 中或使用单独的 Rack::Builder 实例。在这种情况下,你仍然可以通过传递 :session 选项来设置基于会话的防护。

set :protection, :session => true

可用设置

absolute_redirects
如果禁用,Sinatra 将允许相对重定向,但是,Sinatra 将不再符合 RFC 2616(HTTP 1.1),该规范只允许绝对重定向。
如果你的应用程序运行在未正确设置的反向代理后面,请启用此选项。请注意,url 帮助程序仍然会生成绝对 URL,除非你将 false 作为第二个参数传入。
默认情况下禁用。
add_charset
content_type 帮助程序将自动添加字符集信息的 MIME 类型。你应该添加而不是覆盖此选项:settings.add_charset << "application/foobar"
app_file
主应用程序文件路径,用于检测项目根目录、视图和公共文件夹以及内联模板。
bind
要绑定的 IP 地址(默认:0.0.0.0 localhost 如果您的 `environment` 设置为开发)。仅用于内置服务器。
default_content_type
如果未知,则假设的 Content-Type(默认为 "text/html")。设置为 nil 不会在每个响应中设置默认 Content-Type;当这样配置时,您必须在发出内容时手动设置 Content-Type,否则用户代理将不得不嗅探它(或者,如果在 Rack::Protection::XSSHeader 中启用了 nosniff,则假设 application/octet-stream)。
default_encoding
如果未知,则假设的编码(默认为 "utf-8")。
dump_errors
在日志中显示错误。默认情况下启用,除非环境为“测试”。
environment
当前环境。默认为 ENV['APP_ENV'],如果不可用,则默认为 "development"
logging
使用记录器。
lock
在每个请求周围放置一个锁,仅在每个 Ruby 进程并发地处理一个请求。
如果您的应用程序不是线程安全的,则启用。默认情况下禁用。
method_override
使用 _method 魔法允许在不支持它的浏览器中使用 put/delete 表单。
mustermann_opts
传递给 Mustermann.new 的默认选项哈希,用于编译路由路径。
port
要监听的端口。仅用于内置服务器。
prefixed_redirects
是否在没有给出绝对路径的情况下将 request.script_name 插入重定向。这样 redirect '/foo' 就会像 redirect to('/foo') 一样工作。默认情况下禁用。
protection
是否启用 Web 攻击防护。请参阅上面的防护部分。
public_dir
public_folder 的别名。见下文。
public_folder
提供公共文件的文件夹路径。仅在启用静态文件服务时使用(请参阅下面的 static 设置)。如果未设置,则从 app_file 设置推断。
quiet
禁用 Sinatra 的启动和停止命令生成的日志。默认情况下为 false
reload_templates
是否在请求之间重新加载模板。在开发模式下启用。
root
项目根文件夹的路径。如果未设置,则从 app_file 设置推断。
raise_errors
引发未处理的错误(将停止应用程序)。默认情况下,当 environment 设置为 "test" 时启用,否则禁用。
任何明确定义的错误处理程序始终会覆盖此设置。请参阅下面的“错误”部分。
run
如果启用,Sinatra 将处理启动 Web 服务器。如果使用 rackup 或其他方法,请勿启用。
running
内置服务器现在是否正在运行?请勿更改此设置!
server
用于内置服务器的服务器或服务器列表。顺序表示优先级,默认值取决于 Ruby 实现。
server_settings
如果您使用的是 WEBrick Web 服务器(可能用于您的开发环境),则可以将选项的哈希传递给 server_settings,例如 SSLEnableSSLVerifyClient。但是,Puma 等 Web 服务器不支持此功能,因此您可以通过在调用 configure 时将其定义为方法来设置 server_settings
sessions
使用 Rack::Session::Cookie 启用基于 Cookie 的会话支持。有关更多信息,请参阅“使用会话”部分。
session_store
使用的 Rack 会话中间件。默认为 Rack::Session::Cookie。有关更多信息,请参阅“使用会话”部分。
show_exceptions
当发生异常时,在浏览器中显示堆栈跟踪。当 environment 设置为 "development" 时默认启用,否则禁用。
也可以设置为 :after_handler,以便在浏览器中显示堆栈跟踪之前触发应用程序指定的错误处理。
static
Sinatra 是否应该处理静态文件的提供。
当使用能够独立执行此操作的服务器时禁用。
禁用将提高性能。
在经典风格中默认启用,在模块化应用程序中禁用。
static_cache_control
当 Sinatra 提供静态文件时,将其设置为将 Cache-Control 标头添加到响应中。使用 cache_control 助手。默认情况下禁用。
在设置多个值时使用显式数组:set :static_cache_control, [:public, :max_age => 300]
threaded
如果设置为 true,将告诉服务器使用 EventMachine.defer 处理请求。
traps
Sinatra 是否应该处理系统信号。
视图
视图文件夹的路径。如果没有设置,则从 app_file 设置推断。
x_cascade
如果没有任何路由匹配,是否设置 X-Cascade 标头。默认为 true

生命周期事件

Sinatra 目前公开了 2 个生命周期事件。一个是在服务器启动时,另一个是在服务器停止时。

它们可以像这样使用

on_start do
  puts "===== Booting up ====="
end

on_stop do
  puts "===== Shutting down ====="
end

请注意,这些回调仅在使用 Sinatra 启动 Web 服务器时才有效。

环境

有三个预定义的 environments"development""production""test"。环境可以通过 APP_ENV 环境变量设置。默认值为 "development"。在 "development" 环境中,所有模板都在请求之间重新加载,并且特殊的 not_founderror 处理程序在您的浏览器中显示堆栈跟踪。在 "production""test" 环境中,模板默认情况下被缓存。

要运行不同的环境,请设置 APP_ENV 环境变量

APP_ENV=production ruby my_app.rb

您可以使用预定义的方法:development?test?production? 来检查当前环境设置

get '/' do
  if settings.development?
    "development!"
  else
    "not development!"
  end
end

错误处理

错误处理程序在与路由和前置过滤器相同的上下文中运行,这意味着您可以使用它提供的所有功能,例如hamlerbhalt等。

未找到

当引发Sinatra::NotFound异常或响应的 状态码为 404 时,将调用not_found 处理程序。

not_found do
  'This is nowhere to be found.'
end

错误

error 处理程序在从路由块或过滤器引发异常时被调用。但请注意,在开发环境中,只有在将 show exceptions 选项设置为:after_handler 时才会运行。

set :show_exceptions, :after_handler

可以使用error 和一个块来定义一个通用的错误处理程序。

error do
  'Sorry there was a nasty error'
end

可以通过sinatra.error Rack 变量获取异常对象。

error do
  'Sorry there was a nasty error - ' + env['sinatra.error'].message
end

将错误类作为参数传递以创建自定义错误的处理程序。

error MyCustomError do
  'So what happened was...' + env['sinatra.error'].message
end

然后,如果发生这种情况

get '/' do
  raise MyCustomError, 'something bad'
end

您将得到这个

So what happened was... something bad

或者,您可以为状态码安装错误处理程序。

error 403 do
  'Access forbidden'
end

get '/secret' do
  403
end

或一个范围。

error 400..510 do
  'Boom'
end

Sinatra 在开发环境下运行时会安装特殊的not_founderror 处理程序,以便在浏览器中显示漂亮的堆栈跟踪和额外的调试信息。

raise_errors 选项的行为

raise_errors 选项为true 时,未处理的错误将在应用程序外部引发。此外,任何原本会被通用错误处理程序捕获的错误都会被引发。

例如,考虑以下配置

# First handler
error MyCustomError do
  'A custom message'
end

# Second handler
error do
  'A catch-all message'
end

如果raise_errorsfalse

  • 当引发MyCustomError 或其子类时,将调用第一个处理程序。HTTP 响应主体将包含"A custom message"
  • 当引发任何其他错误时,将调用第二个处理程序。HTTP 响应主体将包含"A catch-all message"

如果raise_errorstrue

  • 当引发MyCustomError 或其子类时,行为与raise_errorsfalse 时相同,如上所述。
  • 当引发任何其他错误时,不会调用第二个处理程序,并且错误将在应用程序外部引发。
    • 如果环境为production,HTTP 响应主体将包含一个通用的错误消息,例如"An unhandled lowlevel error occurred. The application logs may have details."
    • 如果环境不是production,HTTP 响应主体将包含详细的错误回溯。
    • 无论环境如何,如果show_exceptions 设置为:after_handler,HTTP 响应主体将包含详细的错误回溯。

test 环境中,raise_errors 默认设置为true。这意味着为了编写通用错误处理程序的测试,必须为该特定测试暂时将raise_errors 设置为false

Rack 中间件

Sinatra 运行在 Rack 之上,Rack 是一个用于 Ruby Web 框架的最小标准接口。Rack 最有趣的特性之一是为应用程序开发者提供了对“中间件”的支持,中间件是位于服务器和应用程序之间的组件,用于监控和/或操作 HTTP 请求/响应,以提供各种类型的通用功能。

Sinatra 通过顶层的 use 方法使构建 Rack 中间件管道变得轻而易举。

require 'sinatra'
require 'my_custom_middleware'

use Rack::Lint
use MyCustomMiddleware

get '/hello' do
  'Hello World'
end

use 的语义与 Rack::Builder DSL 中定义的语义相同(最常用于 rackup 文件)。例如,use 方法接受多个/可变参数以及代码块。

use Rack::Auth::Basic do |username, password|
  username == 'admin' && password == 'secret'
end

Rack 附带了各种标准中间件,用于日志记录、调试、URL 路由、身份验证和会话处理。Sinatra 根据配置自动使用其中许多组件,因此您通常不必显式地 use 它们。

您可以在 rackrack-contribRack wiki 中找到有用的中间件。

测试

Sinatra 测试可以使用任何基于 Rack 的测试库或框架编写。推荐使用 Rack::Test

require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'

class MyAppTest < Minitest::Test
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  def test_my_default
    get '/'
    assert_equal 'Hello World!', last_response.body
  end

  def test_with_params
    get '/meet', :name => 'Frank'
    assert_equal 'Hello Frank!', last_response.body
  end

  def test_with_user_agent
    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
    assert_equal "You're using Songbird!", last_response.body
  end
end

注意:如果您在模块化风格中使用 Sinatra,请将上面的 Sinatra::Application 替换为您的应用程序的类名。

Sinatra::Base - 中间件、库和模块化应用程序

在顶层定义您的应用程序对于微型应用程序来说很有效,但在构建可重用组件(如 Rack 中间件、Rails metal、带有服务器组件的简单库,甚至 Sinatra 扩展)时存在相当大的缺点。顶层假设了一种微型应用程序风格的配置(例如,单个应用程序文件、./public./views 目录、日志记录、异常详细信息页面等)。这就是 Sinatra::Base 发挥作用的地方。

require 'sinatra/base'

class MyApp < Sinatra::Base
  set :sessions, true
  set :foo, 'bar'

  get '/' do
    'Hello world!'
  end
end

Sinatra::Base 子类可用的方法与通过顶层 DSL 可用的方法完全相同。大多数顶层应用程序可以通过两个修改转换为 Sinatra::Base 组件。

  • 您的文件应该需要 sinatra/base 而不是 sinatra;否则,所有 Sinatra 的 DSL 方法将被导入到主命名空间。
  • 将您的应用程序的路由、错误处理程序、过滤器和选项放在 Sinatra::Base 的子类中。

Sinatra::Base 是一个空白画布。默认情况下,大多数选项都被禁用,包括内置服务器。有关可用选项及其行为的详细信息,请参阅 配置设置。如果您想要的行为更类似于在顶层定义应用程序时(也称为经典风格),您可以子类化 Sinatra::Application

require 'sinatra/base'

class MyApp < Sinatra::Application
  get '/' do
    'Hello world!'
  end
end

模块化与经典风格

与普遍认知相反,经典风格并没有什么问题。如果它适合您的应用程序,您无需切换到模块化应用程序。

使用经典风格而不是模块化风格的主要缺点是,您每个 Ruby 进程只有一个 Sinatra 应用程序。如果您计划使用多个应用程序,请切换到模块化风格。您无需将模块化风格和经典风格混合使用。

如果您从一种风格切换到另一种风格,您应该注意一些略微不同的默认设置。

设置 经典 模块化 模块化
app_file 文件加载 sinatra 文件子类化 Sinatra::Base 文件子类化 Sinatra::Application
run $0 == app_file false false
logging true false true
method_override true false true
inline_templates true false true
static true File.exist?(public_folder) true

运行模块化应用程序

启动模块化应用程序有两种常见方法,一种是使用 run! 积极启动。

# my_app.rb
require 'sinatra/base'

class MyApp < Sinatra::Base
  # ... app code here ...

  # start the server if ruby file executed directly
  run! if app_file == $0
end

使用

ruby my_app.rb

或者使用 config.ru 文件,它允许使用任何 Rack 处理程序。

# config.ru (run with rackup)
require './my_app'
run MyApp

运行

rackup -p 4567

使用经典风格应用程序和 config.ru

编写您的应用程序文件

# app.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

以及相应的 config.ru

require './app'
run Sinatra::Application

何时使用 config.ru?

如果以下情况,建议使用 config.ru 文件

  • 您想使用不同的 Rack 处理程序(Passenger、Unicorn、Heroku 等)进行部署。
  • 您想使用多个 Sinatra::Base 子类。
  • 您想将 Sinatra 仅用作中间件,而不是用作端点。

您无需仅仅因为切换到模块化风格就切换到 config.ru,并且您无需使用模块化风格来运行 config.ru

使用 Sinatra 作为中间件

Sinatra 不仅能够使用其他 Rack 中间件,而且任何 Sinatra 应用程序都可以反过来作为中间件添加到任何 Rack 端点之前。这个端点可以是另一个 Sinatra 应用程序,也可以是任何其他基于 Rack 的应用程序(Rails/Hanami/Roda/…)。

require 'sinatra/base'

class LoginScreen < Sinatra::Base
  enable :sessions

  get('/login') { haml :login }

  post('/login') do
    if params['name'] == 'admin' && params['password'] == 'admin'
      session['user_name'] = params['name']
    else
      redirect '/login'
    end
  end
end

class MyApp < Sinatra::Base
  # middleware will run before filters
  use LoginScreen

  before do
    unless session['user_name']
      halt "Access denied, please <a href='/login'>login</a>."
    end
  end

  get('/') { "Hello #{session['user_name']}." }
end

动态应用程序创建

有时您想在运行时创建新的应用程序,而无需将它们分配给常量。您可以使用 Sinatra.new 来实现。

require 'sinatra/base'
my_app = Sinatra.new { get('/') { "hi" } }
my_app.run!

它将应用程序作为可选参数继承。

# config.ru (run with rackup)
require 'sinatra/base'

controller = Sinatra.new do
  enable :logging
  helpers MyHelpers
end

map('/a') do
  run Sinatra.new(controller) { get('/') { 'a' } }
end

map('/b') do
  run Sinatra.new(controller) { get('/') { 'b' } }
end

这对于测试 Sinatra 扩展或在自己的库中使用 Sinatra 特别有用。

这也使得将 Sinatra 用作中间件变得非常容易。

require 'sinatra/base'

use Sinatra do
  get('/') { ... }
end

run RailsProject::Application

作用域和绑定

您当前所在的范围决定了哪些方法和变量可用。

应用程序/类范围

每个 Sinatra 应用程序对应于 Sinatra::Base 的一个子类。如果您使用的是顶级 DSL(require 'sinatra'),那么这个类是 Sinatra::Application,否则它就是您显式创建的子类。在类级别,您拥有 getbefore 之类的方法,但您无法访问 requestsession 对象,因为所有请求只有一个应用程序类。

通过 set 创建的选项是类级别的函数。

class MyApp < Sinatra::Base
  # Hey, I'm in the application scope!
  set :foo, 42
  foo # => 42

  get '/foo' do
    # Hey, I'm no longer in the application scope!
  end
end

您在应用程序类主体内部拥有应用程序范围绑定

  • 您的应用程序类主体
  • 扩展定义的方法
  • 传递给helpers的代码块
  • 用作set值的 Proc/代码块
  • 传递给Sinatra.new的代码块

您可以像这样访问范围对象(类)

  • 通过传递给配置块的对象(configure { |c| ... }
  • 来自请求范围内的settings

请求/实例范围

对于每个传入的请求,都会创建一个应用程序类的新的实例,并且所有处理程序块都在该范围内运行。从该范围内,您可以访问requestsession对象,或调用渲染方法,例如erbhaml。您可以通过settings帮助程序从请求范围内访问应用程序范围

class MyApp < Sinatra::Base
  # Hey, I'm in the application scope!
  get '/define_route/:name' do
    # Request scope for '/define_route/:name'
    @value = 42

    settings.get("/#{params['name']}") do
      # Request scope for "/#{params['name']}"
      @value # => nil (not the same request)
    end

    "Route defined!"
  end
end

您在内部拥有请求范围绑定

  • get、head、post、put、delete、options、patch、link和unlink块
  • before和after过滤器
  • 帮助程序方法
  • 模板/视图

委托范围

委托范围只是将方法转发到类范围。但是,它的行为与类范围不完全相同,因为您没有类绑定。只有明确标记为委托的方法可用,并且您不与类范围共享变量/状态(阅读:您有不同的self)。您可以通过调用Sinatra::Delegator.delegate :method_name显式添加方法委托。

您在内部拥有委托范围绑定

  • 如果您执行了require "sinatra",则为顶层绑定
  • 使用Sinatra::Delegator mixin扩展的对象

自己看看代码:这是Sinatra::Delegator mixin正在扩展主对象

命令行

Sinatra应用程序可以直接运行

ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]

选项是

-h # help
-p # set the port (default is 4567)
-o # set the host (default is 0.0.0.0)
-e # set the environment (default is development)
-s # specify rack server/handler (default is puma)
-q # turn on quiet mode for server (default is off)
-x # turn on the mutex lock (default is off)

多线程

改写自Konstantin的这个StackOverflow答案

Sinatra不强加任何并发模型,而是将其留给底层的Rack处理程序(服务器),例如Puma或WEBrick。Sinatra本身是线程安全的,因此如果Rack处理程序使用线程并发模型,就不会有任何问题。

要求

正式支持以下Ruby版本

Ruby
稳定版本完全受支持,建议使用。
TruffleRuby
支持TruffleRuby的最新稳定版本。
JRuby
支持JRuby的最新稳定版本。不建议在JRuby中使用C扩展。

从Sinatra 4.0.0开始,不再支持Ruby 2.7.8之前的版本。

Sinatra 应该可以在任何受选定 Ruby 实现支持的操作系统上运行。

在未正式支持的 Ruby 版本上运行 Sinatra 意味着,如果问题只在那里出现,我们假设这不是我们的问题,而是他们的问题。

最前沿

如果您想使用 Sinatra 的最新最前沿代码,请随时针对主分支运行您的应用程序,它应该相当稳定。

我们还会不时推出预发布 gem,因此您可以执行以下操作:

gem install sinatra --pre

以获取一些最新功能。

使用 Bundler

如果您想使用最新的 Sinatra 运行您的应用程序,建议使用 Bundler

首先,安装 bundler,如果您还没有安装

gem install bundler

然后,在您的项目目录中,创建一个 Gemfile

source 'https://rubygems.org.cn'
gem 'sinatra', :github => 'sinatra/sinatra'

# other dependencies
gem 'haml'                    # for instance, if you use haml

请注意,您需要在 Gemfile 中列出所有应用程序的依赖项。但是,Sinatra 的直接依赖项(Rack 和 Tilt)将由 Bundler 自动获取和添加。

现在您可以像这样运行您的应用程序

bundle exec ruby myapp.rb

版本控制

Sinatra 遵循 语义版本控制,包括 SemVer 和 SemVerTag。

进一步阅读