mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
d255666d49
Signed-off-by: Konstantin Haase <konstantin.mailinglists@googlemail.com>
2012 lines
57 KiB
Text
2012 lines
57 KiB
Text
= Sinatra
|
|
|
|
Sinatra is a DSL for quickly creating web applications in Ruby with minimal
|
|
effort:
|
|
|
|
# myapp.rb
|
|
require 'sinatra'
|
|
|
|
get '/' do
|
|
'Hello world!'
|
|
end
|
|
|
|
Install the gem and run with:
|
|
|
|
gem install sinatra
|
|
ruby -rubygems myapp.rb
|
|
|
|
View at: http://localhost:4567
|
|
|
|
It is recommended to also run <tt>gem install thin</tt>, which Sinatra will
|
|
pick up if available.
|
|
|
|
== Routes
|
|
|
|
In Sinatra, a route is an HTTP method paired with a URL-matching pattern.
|
|
Each route is associated with a block:
|
|
|
|
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
|
|
|
|
Routes are matched in the order they are defined. The first route that
|
|
matches the request is invoked.
|
|
|
|
Route patterns may include named parameters, accessible via the
|
|
<tt>params</tt> hash:
|
|
|
|
get '/hello/:name' do
|
|
# matches "GET /hello/foo" and "GET /hello/bar"
|
|
# params[:name] is 'foo' or 'bar'
|
|
"Hello #{params[:name]}!"
|
|
end
|
|
|
|
You can also access named parameters via block parameters:
|
|
|
|
get '/hello/:name' do |n|
|
|
"Hello #{n}!"
|
|
end
|
|
|
|
Route patterns may also include splat (or wildcard) parameters, accessible
|
|
via the <tt>params[:splat]</tt> array:
|
|
|
|
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
|
|
|
|
Or with block parameters:
|
|
|
|
get '/download/*.*' do |path, ext|
|
|
[path, ext] # => ["path/to/file", "xml"]
|
|
end
|
|
|
|
Route matching with Regular Expressions:
|
|
|
|
get %r{/hello/([\w]+)} do
|
|
"Hello, #{params[:captures].first}!"
|
|
end
|
|
|
|
Or with a block parameter:
|
|
|
|
get %r{/hello/([\w]+)} do |c|
|
|
"Hello, #{c}!"
|
|
end
|
|
|
|
Route patterns may have optional parameters:
|
|
|
|
get '/posts.?:format?' do
|
|
# matches "GET /posts" and any extension "GET /posts.json", "GET /posts.xml" etc.
|
|
end
|
|
|
|
By the way, unless you disable the path traversal attack protection (see below),
|
|
the request path might be modified before matching against your routes.
|
|
|
|
=== Conditions
|
|
|
|
Routes may include a variety of matching conditions, such as the user agent:
|
|
|
|
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
|
|
|
|
Other available conditions are +host_name+ and +provides+:
|
|
|
|
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
|
|
|
|
You can easily define your own conditions:
|
|
|
|
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
|
|
|
|
For a condition that takes multiple values use a 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
|
|
|
|
=== Return Values
|
|
|
|
The return value of a route block determines at least the response body passed
|
|
on to the HTTP client, or at least the next middleware in the Rack stack.
|
|
Most commonly, this is a string, as in the above examples. But other values are
|
|
also accepted.
|
|
|
|
You can return any object that would either be a valid Rack response, Rack
|
|
body object or HTTP status code:
|
|
|
|
* An Array with three elements: <tt>[status (Fixnum), headers (Hash), response
|
|
body (responds to #each)]</tt>
|
|
* An Array with two elements: <tt>[status (Fixnum), response body (responds to
|
|
#each)]</tt>
|
|
* An object that responds to <tt>#each</tt> and passes nothing but strings to
|
|
the given block
|
|
* A Fixnum representing the status code
|
|
|
|
That way we can, for instance, easily implement a streaming example:
|
|
|
|
class Stream
|
|
def each
|
|
100.times { |i| yield "#{i}\n" }
|
|
end
|
|
end
|
|
|
|
get('/') { Stream.new }
|
|
|
|
You can also use the +stream+ helper method (described below) to reduce boiler
|
|
plate and embed the streaming logic in the route.
|
|
|
|
=== Custom Route Matchers
|
|
|
|
As shown above, Sinatra ships with built-in support for using String patterns
|
|
and regular expressions as route matches. However, it does not stop there. You
|
|
can easily define your own matchers:
|
|
|
|
class AllButPattern
|
|
Match = Struct.new(:captures)
|
|
|
|
def initialize(except)
|
|
@except = except
|
|
@captures = Match.new([])
|
|
end
|
|
|
|
def match(str)
|
|
@captures unless @except === str
|
|
end
|
|
end
|
|
|
|
def all_but(pattern)
|
|
AllButPattern.new(pattern)
|
|
end
|
|
|
|
get all_but("/index") do
|
|
# ...
|
|
end
|
|
|
|
Note that the above example might be over-engineered, as it can also be
|
|
expressed as:
|
|
|
|
get // do
|
|
pass if request.path_info == "/index"
|
|
# ...
|
|
end
|
|
|
|
Or, using negative look ahead:
|
|
|
|
get %r{^(?!/index$)} do
|
|
# ...
|
|
end
|
|
|
|
== Static Files
|
|
|
|
Static files are served from the <tt>./public</tt> directory. You can specify
|
|
a different location by setting the <tt>:public_folder</tt> option:
|
|
|
|
set :public_folder, File.dirname(__FILE__) + '/static'
|
|
|
|
Note that the public directory name is not included in the URL. A file
|
|
<tt>./public/css/style.css</tt> is made available as
|
|
<tt>http://example.com/css/style.css</tt>.
|
|
|
|
Use the <tt>:static_cache_control</tt> setting (see below) to add
|
|
<tt>Cache-Control</tt> header info.
|
|
|
|
== Views / Templates
|
|
|
|
Each template language is exposed as via its own rendering method. These
|
|
methods simply return a string:
|
|
|
|
get '/' do
|
|
erb :index
|
|
end
|
|
|
|
This renders <tt>views/index.erb</tt>.
|
|
|
|
Instead of a template name, you can also just pass in the template content
|
|
directly:
|
|
|
|
get '/' do
|
|
code = "<%= Time.now %>"
|
|
erb code
|
|
end
|
|
|
|
Templates take a second argument, the options hash:
|
|
|
|
get '/' do
|
|
erb :index, :layout => :post
|
|
end
|
|
|
|
This will render <tt>views/index.erb</tt> embedded in the
|
|
<tt>views/post.erb</tt> (default is <tt>views/layout.erb</tt>, if it exists).
|
|
|
|
Any options not understood by Sinatra will be passed on to the template
|
|
engine:
|
|
|
|
get '/' do
|
|
haml :index, :format => :html5
|
|
end
|
|
|
|
You can also set options per template language in general:
|
|
|
|
set :haml, :format => :html5
|
|
|
|
get '/' do
|
|
haml :index
|
|
end
|
|
|
|
Options passed to the render method override options set via +set+.
|
|
|
|
Available Options:
|
|
|
|
[locals]
|
|
List of locals passed to the document. Handy with partials.
|
|
Example: <tt>erb "<%= foo %>", :locals => {:foo => "bar"}</tt>
|
|
|
|
[default_encoding]
|
|
String encoding to use if uncertain. Defaults to
|
|
<tt>settings.default_encoding</tt>.
|
|
|
|
[views]
|
|
Views folder to load templates from. Defaults to <tt>settings.views</tt>.
|
|
|
|
[layout]
|
|
Whether to use a layout (+true+ or +false+), if it's a Symbol, specifies
|
|
what template to use. Example: <tt>erb :index, :layout => !request.xhr?</tt>
|
|
|
|
[content_type]
|
|
Content-Type the template produces, default depends on template language.
|
|
|
|
[scope]
|
|
Scope to render template under. Defaults to the application instance. If you
|
|
change this, instance variables and helper methods will not be available.
|
|
|
|
[layout_engine]
|
|
Template engine to use for rendering the layout. Useful for languages that
|
|
do not support layouts otherwise. Defaults to the engine used for the
|
|
template. Example: <tt>set :rdoc, :layout_engine => :erb</tt>
|
|
|
|
Templates are assumed to be located directly under the <tt>./views</tt>
|
|
directory. To use a different views directory:
|
|
|
|
set :views, settings.root + '/templates'
|
|
|
|
One important thing to remember is that you always have to reference
|
|
templates with symbols, even if they're in a subdirectory (in this
|
|
case, use <tt>:'subdir/template'</tt>). You must use a symbol because
|
|
otherwise rendering methods will render any strings passed to them
|
|
directly.
|
|
|
|
=== Available Template Languages
|
|
|
|
Some languages have multiple implementations. To specify what implementation
|
|
to use (and to be thread-safe), you should simply require it first:
|
|
|
|
require 'rdiscount' # or require 'bluecloth'
|
|
get('/') { markdown :index }
|
|
|
|
=== Haml Templates
|
|
|
|
Dependency:: {haml}[http://haml-lang.com/]
|
|
File Extensions:: <tt>.haml</tt>
|
|
Example:: <tt>haml :index, :format => :html5</tt>
|
|
|
|
=== Erb Templates
|
|
|
|
Dependency:: {erubis}[http://www.kuwata-lab.com/erubis/] or
|
|
erb (included in Ruby)
|
|
File Extensions:: <tt>.erb</tt>, <tt>.rhtml</tt> or <tt>.erubis</tt> (Erubis
|
|
only)
|
|
Example:: <tt>erb :index</tt>
|
|
|
|
=== Builder Templates
|
|
|
|
Dependency:: {builder}[http://builder.rubyforge.org/]
|
|
File Extensions:: <tt>.builder</tt>
|
|
Example:: <tt>builder { |xml| xml.em "hi" }</tt>
|
|
|
|
It also takes a block for inline templates (see example).
|
|
|
|
=== Nokogiri Templates
|
|
|
|
Dependency:: {nokogiri}[http://nokogiri.org/]
|
|
File Extensions:: <tt>.nokogiri</tt>
|
|
Example:: <tt>nokogiri { |xml| xml.em "hi" }</tt>
|
|
|
|
It also takes a block for inline templates (see example).
|
|
|
|
=== Sass Templates
|
|
|
|
Dependency:: {sass}[http://sass-lang.com/]
|
|
File Extensions:: <tt>.sass</tt>
|
|
Example:: <tt>sass :stylesheet, :style => :expanded</tt>
|
|
|
|
=== SCSS Templates
|
|
|
|
Dependency:: {sass}[http://sass-lang.com/]
|
|
File Extensions:: <tt>.scss</tt>
|
|
Example:: <tt>scss :stylesheet, :style => :expanded</tt>
|
|
|
|
=== Less Templates
|
|
|
|
Dependency:: {less}[http://www.lesscss.org/]
|
|
File Extensions:: <tt>.less</tt>
|
|
Example:: <tt>less :stylesheet</tt>
|
|
|
|
=== Liquid Templates
|
|
|
|
Dependency:: {liquid}[http://www.liquidmarkup.org/]
|
|
File Extensions:: <tt>.liquid</tt>
|
|
Example:: <tt>liquid :index, :locals => { :key => 'value' }</tt>
|
|
|
|
Since you cannot call Ruby methods (except for +yield+) from a Liquid
|
|
template, you almost always want to pass locals to it.
|
|
|
|
=== Markdown Templates
|
|
|
|
Dependency:: {rdiscount}[https://github.com/rtomayko/rdiscount],
|
|
{redcarpet}[https://github.com/tanoku/redcarpet],
|
|
{bluecloth}[http://deveiate.org/projects/BlueCloth],
|
|
{kramdown}[http://kramdown.rubyforge.org/] *or*
|
|
{maruku}[http://maruku.rubyforge.org/]
|
|
File Extensions:: <tt>.markdown</tt>, <tt>.mkd</tt> and <tt>.md</tt>
|
|
Example:: <tt>markdown :index, :layout_engine => :erb</tt>
|
|
|
|
It is not possible to call methods from markdown, nor to pass locals to it.
|
|
You therefore will usually use it in combination with another rendering
|
|
engine:
|
|
|
|
erb :overview, :locals => { :text => markdown(:introduction) }
|
|
|
|
Note that you may also call the +markdown+ method from within other templates:
|
|
|
|
%h1 Hello From Haml!
|
|
%p= markdown(:greetings)
|
|
|
|
Since you cannot call Ruby from Markdown, you cannot use layouts written in
|
|
Markdown. However, it is possible to use another rendering engine for the
|
|
template than for the layout by passing the <tt>:layout_engine</tt> option.
|
|
|
|
=== Textile Templates
|
|
|
|
Dependency:: {RedCloth}[http://redcloth.org/]
|
|
File Extensions:: <tt>.textile</tt>
|
|
Example:: <tt>textile :index, :layout_engine => :erb</tt>
|
|
|
|
It is not possible to call methods from textile, nor to pass locals to it. You
|
|
therefore will usually use it in combination with another rendering engine:
|
|
|
|
erb :overview, :locals => { :text => textile(:introduction) }
|
|
|
|
Note that you may also call the +textile+ method from within other templates:
|
|
|
|
%h1 Hello From Haml!
|
|
%p= textile(:greetings)
|
|
|
|
Since you cannot call Ruby from Textile, you cannot use layouts written in
|
|
Textile. However, it is possible to use another rendering engine for the
|
|
template than for the layout by passing the <tt>:layout_engine</tt> option.
|
|
|
|
=== RDoc Templates
|
|
|
|
Dependency:: {rdoc}[http://rdoc.rubyforge.org/]
|
|
File Extensions:: <tt>.rdoc</tt>
|
|
Example:: <tt>rdoc :README, :layout_engine => :erb</tt>
|
|
|
|
It is not possible to call methods from rdoc, nor to pass locals to it. You
|
|
therefore will usually use it in combination with another rendering engine:
|
|
|
|
erb :overview, :locals => { :text => rdoc(:introduction) }
|
|
|
|
Note that you may also call the +rdoc+ method from within other templates:
|
|
|
|
%h1 Hello From Haml!
|
|
%p= rdoc(:greetings)
|
|
|
|
Since you cannot call Ruby from RDoc, you cannot use layouts written in
|
|
RDoc. However, it is possible to use another rendering engine for the
|
|
template than for the layout by passing the <tt>:layout_engine</tt> option.
|
|
|
|
=== Radius Templates
|
|
|
|
Dependency:: {radius}[http://radius.rubyforge.org/]
|
|
File Extensions:: <tt>.radius</tt>
|
|
Example:: <tt>radius :index, :locals => { :key => 'value' }</tt>
|
|
|
|
Since you cannot call Ruby methods directly from a Radius template, you almost
|
|
always want to pass locals to it.
|
|
|
|
=== Markaby Templates
|
|
|
|
Dependency:: {markaby}[http://markaby.github.com/]
|
|
File Extensions:: <tt>.mab</tt>
|
|
Example:: <tt>markaby { h1 "Welcome!" }</tt>
|
|
|
|
It also takes a block for inline templates (see example).
|
|
|
|
=== Slim Templates
|
|
|
|
Dependency:: {slim}[http://slim-lang.com/]
|
|
File Extensions:: <tt>.slim</tt>
|
|
Example:: <tt>slim :index</tt>
|
|
|
|
=== Creole Templates
|
|
|
|
Dependency:: {creole}[https://github.com/minad/creole]
|
|
File Extensions:: <tt>.creole</tt>
|
|
Example:: <tt>creole :wiki, :layout_engine => :erb</tt>
|
|
|
|
It is not possible to call methods from creole, nor to pass locals to it. You
|
|
therefore will usually use it in combination with another rendering engine:
|
|
|
|
erb :overview, :locals => { :text => creole(:introduction) }
|
|
|
|
Note that you may also call the +creole+ method from within other templates:
|
|
|
|
%h1 Hello From Haml!
|
|
%p= creole(:greetings)
|
|
|
|
Since you cannot call Ruby from Creole, you cannot use layouts written in
|
|
Creole. However, it is possible to use another rendering engine for the
|
|
template than for the layout by passing the <tt>:layout_engine</tt> option.
|
|
|
|
=== CoffeeScript Templates
|
|
|
|
Dependency:: {coffee-script}[https://github.com/josh/ruby-coffee-script]
|
|
and a {way to execute javascript}[https://github.com/sstephenson/execjs/blob/master/README.md#readme]
|
|
File Extensions:: <tt>.coffee</tt>
|
|
Example:: <tt>coffee :index</tt>
|
|
|
|
=== Yajl Templates
|
|
|
|
Dependency:: {yajl-ruby}[https://github.com/brianmario/yajl-ruby]
|
|
File Extensions:: <tt>.yajl</tt>
|
|
Example:: <tt>yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource' </tt>
|
|
|
|
The template source is evaluated as a Ruby string, and the resulting json variable is converted #to_json.
|
|
|
|
json = { :foo => 'bar' }
|
|
json[:baz] = key
|
|
|
|
The <tt>:callback</tt> and <tt>:variable</tt> options can be used to decorate the rendered object.
|
|
|
|
var resource = {"foo":"bar","baz":"qux"}; present(resource);
|
|
|
|
=== Embedded Templates
|
|
|
|
get '/' do
|
|
haml '%div.title Hello World'
|
|
end
|
|
|
|
Renders the embedded template string.
|
|
|
|
=== Accessing Variables in Templates
|
|
|
|
Templates are evaluated within the same context as route handlers. Instance
|
|
variables set in route handlers are directly accessible by templates:
|
|
|
|
get '/:id' do
|
|
@foo = Foo.find(params[:id])
|
|
haml '%h1= @foo.name'
|
|
end
|
|
|
|
Or, specify an explicit Hash of local variables:
|
|
|
|
get '/:id' do
|
|
foo = Foo.find(params[:id])
|
|
haml '%h1= bar.name', :locals => { :bar => foo }
|
|
end
|
|
|
|
This is typically used when rendering templates as partials from within
|
|
other templates.
|
|
|
|
=== Inline Templates
|
|
|
|
Templates may be defined at the end of the source file:
|
|
|
|
require 'sinatra'
|
|
|
|
get '/' do
|
|
haml :index
|
|
end
|
|
|
|
__END__
|
|
|
|
@@ layout
|
|
%html
|
|
= yield
|
|
|
|
@@ index
|
|
%div.title Hello world.
|
|
|
|
NOTE: Inline templates defined in the source file that requires sinatra are
|
|
automatically loaded. Call <tt>enable :inline_templates</tt> explicitly if you
|
|
have inline templates in other source files.
|
|
|
|
=== Named Templates
|
|
|
|
Templates may also be defined using the top-level <tt>template</tt> method:
|
|
|
|
template :layout do
|
|
"%html\n =yield\n"
|
|
end
|
|
|
|
template :index do
|
|
'%div.title Hello World!'
|
|
end
|
|
|
|
get '/' do
|
|
haml :index
|
|
end
|
|
|
|
If a template named "layout" exists, it will be used each time a template
|
|
is rendered. You can individually disable layouts by passing
|
|
<tt>:layout => false</tt> or disable them by default via
|
|
<tt>set :haml, :layout => false</tt>:
|
|
|
|
get '/' do
|
|
haml :index, :layout => !request.xhr?
|
|
end
|
|
|
|
=== Associating File Extensions
|
|
|
|
To associate a file extension with a template engine, use
|
|
<tt>Tilt.register</tt>. For instance, if you like to use the file extension
|
|
+tt+ for Textile templates, you can do the following:
|
|
|
|
Tilt.register :tt, Tilt[:textile]
|
|
|
|
=== Adding Your Own Template Engine
|
|
|
|
First, register your engine with Tilt, then create a rendering method:
|
|
|
|
Tilt.register :myat, MyAwesomeTemplateEngine
|
|
|
|
helpers do
|
|
def myat(*args) render(:myat, *args) end
|
|
end
|
|
|
|
get '/' do
|
|
myat :index
|
|
end
|
|
|
|
Renders <tt>./views/index.myat</tt>. See https://github.com/rtomayko/tilt to
|
|
learn more about Tilt.
|
|
|
|
== Filters
|
|
|
|
Before filters are evaluated before each request within the same
|
|
context as the routes will be and can modify the request and response. Instance
|
|
variables set in filters are accessible by routes and templates:
|
|
|
|
before do
|
|
@note = 'Hi!'
|
|
request.path_info = '/foo/bar/baz'
|
|
end
|
|
|
|
get '/foo/*' do
|
|
@note #=> 'Hi!'
|
|
params[:splat] #=> 'bar/baz'
|
|
end
|
|
|
|
After filters are evaluated after each request within the same context and can
|
|
also modify the request and response. Instance variables set in before filters
|
|
and routes are accessible by after filters:
|
|
|
|
after do
|
|
puts response.status
|
|
end
|
|
|
|
Note: Unless you use the +body+ method rather than just returning a String from
|
|
the routes, the body will not yet be available in the after filter, since it is
|
|
generated later on.
|
|
|
|
Filters optionally take a pattern, causing them to be evaluated only if the
|
|
request path matches that pattern:
|
|
|
|
before '/protected/*' do
|
|
authenticate!
|
|
end
|
|
|
|
after '/create/:slug' do |slug|
|
|
session[:last_slug] = slug
|
|
end
|
|
|
|
Like routes, filters also take conditions:
|
|
|
|
before :agent => /Songbird/ do
|
|
# ...
|
|
end
|
|
|
|
after '/blog/*', :host_name => 'example.com' do
|
|
# ...
|
|
end
|
|
|
|
== Helpers
|
|
|
|
Use the top-level <tt>helpers</tt> method to define helper methods for use in
|
|
route handlers and templates:
|
|
|
|
helpers do
|
|
def bar(name)
|
|
"#{name}bar"
|
|
end
|
|
end
|
|
|
|
get '/:name' do
|
|
bar(params[:name])
|
|
end
|
|
|
|
Alternatively, helper methods can be separately defined in a module:
|
|
|
|
module FooUtils
|
|
def foo(name) "#{name}foo" end
|
|
end
|
|
|
|
module BarUtils
|
|
def bar(name) "#{name}bar" end
|
|
end
|
|
|
|
helpers FooUtils, BarUtils
|
|
|
|
The effect is the same as including the modules in the application class.
|
|
|
|
=== Using Sessions
|
|
|
|
A session is used to keep state during requests. If activated, you have one
|
|
session hash per user session:
|
|
|
|
enable :sessions
|
|
|
|
get '/' do
|
|
"value = " << session[:value].inspect
|
|
end
|
|
|
|
get '/:value' do
|
|
session[:value] = params[:value]
|
|
end
|
|
|
|
Note that <tt>enable :sessions</tt> actually stores all data in a cookie. This
|
|
might not always be what you want (storing lots of data will increase your
|
|
traffic, for instance). You can use any Rack session middleware: in order to
|
|
do so, do *not* call <tt>enable :sessions</tt>, but instead pull in your
|
|
middleware of choice as you would any other middleware:
|
|
|
|
use Rack::Session::Pool, :expire_after => 2592000
|
|
|
|
get '/' do
|
|
"value = " << session[:value].inspect
|
|
end
|
|
|
|
get '/:value' do
|
|
session[:value] = params[:value]
|
|
end
|
|
|
|
To improve security, the session data in the cookie is signed with a session
|
|
secret. A random secret is generate for you by Sinatra. However, since this
|
|
secret will change with every start of your application, you might want to
|
|
set the secret yourself, so all your application instances share it:
|
|
|
|
set :session_secret, 'super secret'
|
|
|
|
If you want to configure it further, you may also store a hash with options in
|
|
the +sessions+ setting:
|
|
|
|
set :sessions, :domain => 'foo.com'
|
|
|
|
=== Halting
|
|
|
|
To immediately stop a request within a filter or route use:
|
|
|
|
halt
|
|
|
|
You can also specify the status when halting:
|
|
|
|
halt 410
|
|
|
|
Or the body:
|
|
|
|
halt 'this will be the body'
|
|
|
|
Or both:
|
|
|
|
halt 401, 'go away!'
|
|
|
|
With headers:
|
|
|
|
halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
|
|
|
|
It is of course possible to combine a template with +halt+:
|
|
|
|
halt erb(:error)
|
|
|
|
=== Passing
|
|
|
|
A route can punt processing to the next matching route using <tt>pass</tt>:
|
|
|
|
get '/guess/:who' do
|
|
pass unless params[:who] == 'Frank'
|
|
'You got me!'
|
|
end
|
|
|
|
get '/guess/*' do
|
|
'You missed!'
|
|
end
|
|
|
|
The route block is immediately exited and control continues with the next
|
|
matching route. If no matching route is found, a 404 is returned.
|
|
|
|
=== Triggering Another Route
|
|
|
|
Sometimes +pass+ is not what you want, instead you would like to get the result
|
|
of calling another route. Simply use +call+ to achieve this:
|
|
|
|
get '/foo' do
|
|
status, headers, body = call env.merge("PATH_INFO" => '/bar')
|
|
[status, headers, body.map(&:upcase)]
|
|
end
|
|
|
|
get '/bar' do
|
|
"bar"
|
|
end
|
|
|
|
Note that in the example above, you would ease testing and increase performance
|
|
by simply moving <tt>"bar"</tt> into a helper used by both <tt>/foo</tt>
|
|
and <tt>/bar</tt>.
|
|
|
|
If you want the request to be sent to the same application instance rather than
|
|
a duplicate, use <tt>call!</tt> instead of <tt>call</tt>.
|
|
|
|
Check out the Rack specification if you want to learn more about <tt>call</tt>.
|
|
|
|
=== Setting Body, Status Code and Headers
|
|
|
|
It is possible and recommended to set the status code and response body with the
|
|
return value of the route block. However, in some scenarios you might want to
|
|
set the body at an arbitrary point in the execution flow. You can do so with the
|
|
+body+ helper method. If you do so, you can use that method from there on to
|
|
access the body:
|
|
|
|
get '/foo' do
|
|
body "bar"
|
|
end
|
|
|
|
after do
|
|
puts body
|
|
end
|
|
|
|
It is also possible to pass a block to +body+, which will be executed by the
|
|
Rack handler (this can be used to implement streaming, see "Return Values").
|
|
|
|
Similar to the body, you can also set the status code and headers:
|
|
|
|
get '/foo' do
|
|
status 418
|
|
headers \
|
|
"Allow" => "BREW, POST, GET, PROPFIND, WHEN",
|
|
"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
|
|
body "I'm a tea pot!"
|
|
end
|
|
|
|
Like +body+, +headers+ and +status+ with no arguments can be used to access
|
|
their current values.
|
|
|
|
=== Streaming Responses
|
|
|
|
Sometimes you want to start sending out data while still generating parts of
|
|
the response body. In extreme examples, you want to keep sending data until
|
|
the client closes the connection. You can use the +stream+ helper to avoid
|
|
creating your own wrapper:
|
|
|
|
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
|
|
|
|
This allows you to implement streaming APIs,
|
|
{Server Sent Events}[http://dev.w3.org/html5/eventsource/] and can be used as
|
|
basis for {WebSockets}[http://en.wikipedia.org/wiki/WebSocket]. It can also be
|
|
used to increase throughput if some but not all content depends on a slow
|
|
resource.
|
|
|
|
Note that the streaming behavior, especially the number of concurrent request,
|
|
highly depends on the web server used to serve the application. Some servers,
|
|
like WEBRick, might not even support streaming at all. If the server does not
|
|
support streaming, the body will be sent all at once after the block passed to
|
|
+stream+ finished executing. Streaming does not work at all with Shotgun.
|
|
|
|
If the optional parameter is set to +keep_open+, it will not call +close+ on
|
|
the stream object, allowing you to close it at any later point in the
|
|
execution flow. This only works on evented servers, like Thin and Rainbows.
|
|
Other servers will still close the stream:
|
|
|
|
set :server, :thin
|
|
connections = []
|
|
|
|
get '/' do
|
|
# keep stream open
|
|
stream(:keep_open) { |out| connections << out }
|
|
end
|
|
|
|
post '/' do
|
|
# write to all open streams
|
|
connections.each { |out| out << params[:message] << "\n" }
|
|
"message sent"
|
|
end
|
|
|
|
=== Logging
|
|
|
|
In the request scope, the +logger+ helper exposes a +Logger+ instance:
|
|
|
|
get '/' do
|
|
logger.info "loading data"
|
|
# ...
|
|
end
|
|
|
|
This logger will automatically take your Rack handler's logging settings into
|
|
account. If logging is disabled, this method will return a dummy object, so
|
|
you do not have to worry in your routes and filters about it.
|
|
|
|
Note that logging is only enabled for <tt>Sinatra::Application</tt> by
|
|
default, so if you inherit from <tt>Sinatra::Base</tt>, you probably want to
|
|
enable it yourself:
|
|
|
|
class MyApp < Sinatra::Base
|
|
configure :production, :development do
|
|
enable :logging
|
|
end
|
|
end
|
|
|
|
To avoid any logging middleware to be set up, set the +logging+ setting to
|
|
+nil+. However, keep in mind that +logger+ will in that case return +nil+. A
|
|
common use case is when you want to set your own logger. Sinatra will use
|
|
whatever it will find in <tt>env['rack.logger']</tt>.
|
|
|
|
=== Mime Types
|
|
|
|
When using <tt>send_file</tt> or static files you may have mime types Sinatra
|
|
doesn't understand. Use +mime_type+ to register them by file extension:
|
|
|
|
configure do
|
|
mime_type :foo, 'text/foo'
|
|
end
|
|
|
|
You can also use it with the +content_type+ helper:
|
|
|
|
get '/' do
|
|
content_type :foo
|
|
"foo foo foo"
|
|
end
|
|
|
|
=== Generating URLs
|
|
|
|
For generating URLs you should use the +url+ helper method, for instance, in
|
|
Haml:
|
|
|
|
%a{:href => url('/foo')} foo
|
|
|
|
It takes reverse proxies and Rack routers into account, if present.
|
|
|
|
This method is also aliased to +to+ (see below for an example).
|
|
|
|
=== Browser Redirect
|
|
|
|
You can trigger a browser redirect with the +redirect+ helper method:
|
|
|
|
get '/foo' do
|
|
redirect to('/bar')
|
|
end
|
|
|
|
Any additional parameters are handled like arguments passed to +halt+:
|
|
|
|
redirect to('/bar'), 303
|
|
redirect 'http://google.com', 'wrong place, buddy'
|
|
|
|
You can also easily redirect back to the page the user came from with
|
|
<tt>redirect back</tt>:
|
|
|
|
get '/foo' do
|
|
"<a href='/bar'>do something</a>"
|
|
end
|
|
|
|
get '/bar' do
|
|
do_something
|
|
redirect back
|
|
end
|
|
|
|
To pass arguments with a redirect, either add them to the query:
|
|
|
|
redirect to('/bar?sum=42')
|
|
|
|
Or use a session:
|
|
|
|
enable :sessions
|
|
|
|
get '/foo' do
|
|
session[:secret] = 'foo'
|
|
redirect to('/bar')
|
|
end
|
|
|
|
get '/bar' do
|
|
session[:secret]
|
|
end
|
|
|
|
=== Cache Control
|
|
|
|
Setting your headers correctly is the foundation for proper HTTP caching.
|
|
|
|
You can easily set the Cache-Control header with like this:
|
|
|
|
get '/' do
|
|
cache_control :public
|
|
"cache it!"
|
|
end
|
|
|
|
Pro tip: Set up caching in a before filter:
|
|
|
|
before do
|
|
cache_control :public, :must_revalidate, :max_age => 60
|
|
end
|
|
|
|
If you are using the +expires+ helper to set the corresponding header,
|
|
<tt>Cache-Control</tt> will be set automatically for you:
|
|
|
|
before do
|
|
expires 500, :public, :must_revalidate
|
|
end
|
|
|
|
To properly use caches, you should consider using +etag+ or +last_modified+.
|
|
It is recommended to call those helpers *before* doing heavy lifting, as they
|
|
will immediately flush a response if the client already has the current
|
|
version in its cache:
|
|
|
|
get '/article/:id' do
|
|
@article = Article.find params[:id]
|
|
last_modified @article.updated_at
|
|
etag @article.sha1
|
|
erb :article
|
|
end
|
|
|
|
It is also possible to use a
|
|
{weak ETag}[http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation]:
|
|
|
|
etag @article.sha1, :weak
|
|
|
|
These helpers will not do any caching for you, but rather feed the necessary
|
|
information to your cache. If you are looking for a quick reverse-proxy caching
|
|
solution, try {rack-cache}[http://rtomayko.github.com/rack-cache/]:
|
|
|
|
require "rack/cache"
|
|
require "sinatra"
|
|
|
|
use Rack::Cache
|
|
|
|
get '/' do
|
|
cache_control :public, :max_age => 36000
|
|
sleep 5
|
|
"hello"
|
|
end
|
|
|
|
Use the <tt>:static_cache_control</tt> setting (see below) to add
|
|
<tt>Cache-Control</tt> header info to static files.
|
|
|
|
According to RFC 2616 your application should behave differently if the If-Match
|
|
or If-None-Match header is set to <tt>*</tt> depending on whether the resource
|
|
requested is already in existence. Sinatra assumes resources for safe (like get)
|
|
and idempotent (like put) requests are already in existence, whereas other
|
|
resources (for instance for post requests), are treated as new resources. You
|
|
can change this behavior by passing in a <tt>:new_resource</tt> option:
|
|
|
|
get '/create' do
|
|
etag '', :new_resource => true
|
|
Article.create
|
|
erb :new_article
|
|
end
|
|
|
|
If you still want to use a weak ETag, pass in a <tt>:kind</tt> option:
|
|
|
|
etag '', :new_resource => true, :kind => :weak
|
|
|
|
=== Sending Files
|
|
|
|
For sending files, you can use the <tt>send_file</tt> helper method:
|
|
|
|
get '/' do
|
|
send_file 'foo.png'
|
|
end
|
|
|
|
It also takes a couple of options:
|
|
|
|
send_file 'foo.png', :type => :jpg
|
|
|
|
The options are:
|
|
|
|
[filename]
|
|
file name, in response, defaults to the real file name.
|
|
|
|
[last_modified]
|
|
value for Last-Modified header, defaults to the file's mtime.
|
|
|
|
[type]
|
|
content type to use, guessed from the file extension if missing.
|
|
|
|
[disposition]
|
|
used for Content-Disposition, possible values: +nil+ (default),
|
|
<tt>:attachment</tt> and <tt>:inline</tt>
|
|
|
|
[length]
|
|
Content-Length header, defaults to file size.
|
|
|
|
[status]
|
|
Status code to be send. Useful when sending a static file as an error page.
|
|
|
|
If supported by the Rack handler, other means than streaming from the Ruby
|
|
process will be used. If you use this helper method, Sinatra will automatically
|
|
handle range requests.
|
|
|
|
=== Accessing the Request Object
|
|
|
|
The incoming request object can be accessed from request level (filter, routes,
|
|
error handlers) through the <tt>request</tt> method:
|
|
|
|
# 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_HEADER"] # value of SOME_HEADER header
|
|
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
|
|
|
|
Some options, like <tt>script_name</tt> or <tt>path_info</tt>, can also be
|
|
written:
|
|
|
|
before { request.path_info = "/" }
|
|
|
|
get "/" do
|
|
"all requests end up here"
|
|
end
|
|
|
|
The <tt>request.body</tt> is an IO or StringIO object:
|
|
|
|
post "/api" do
|
|
request.body.rewind # in case someone already read it
|
|
data = JSON.parse request.body.read
|
|
"Hello #{data['name']}!"
|
|
end
|
|
|
|
=== Attachments
|
|
|
|
You can use the +attachment+ helper to tell the browser the response should be
|
|
stored on disk rather than displayed in the browser:
|
|
|
|
get '/' do
|
|
attachment
|
|
"store it!"
|
|
end
|
|
|
|
You can also pass it a file name:
|
|
|
|
get '/' do
|
|
attachment "info.txt"
|
|
"store it!"
|
|
end
|
|
|
|
=== Dealing with Date and Time
|
|
|
|
Sinatra offers a +time_for+ helper method, which, from the given value
|
|
generates a Time object. It is also able to convert +DateTime+, +Date+ and
|
|
similar classes:
|
|
|
|
get '/' do
|
|
pass if Time.now > time_for('Dec 23, 2012')
|
|
"still time"
|
|
end
|
|
|
|
This method is used internally by +expires+, +last_modified+ and akin. You can
|
|
therefore easily extend the behavior of those methods by overriding +time_for+
|
|
in your application:
|
|
|
|
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
|
|
|
|
=== Looking Up Template Files
|
|
|
|
The <tt>find_template</tt> helper is used to find template files for rendering:
|
|
|
|
find_template settings.views, 'foo', Tilt[:haml] do |file|
|
|
puts "could be #{file}"
|
|
end
|
|
|
|
This is not really useful. But it is useful that you can actually override this
|
|
method to hook in your own lookup mechanism. For instance, if you want to be
|
|
able to use more than one view directory:
|
|
|
|
set :views, ['views', 'templates']
|
|
|
|
helpers do
|
|
def find_template(views, name, engine, &block)
|
|
Array(views).each { |v| super(v, name, engine, &block) }
|
|
end
|
|
end
|
|
|
|
Another example would be using different directories for different engines:
|
|
|
|
set :views, :sass => 'views/sass', :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
|
|
|
|
You can also easily wrap this up in an extension and share with others!
|
|
|
|
Note that <tt>find_template</tt> does not check if the file really exists but
|
|
rather calls the given block for all possible paths. This is not a performance
|
|
issue, since +render+ will use +break+ as soon as a file is found. Also,
|
|
template locations (and content) will be cached if you are not running in
|
|
development mode. You should keep that in mind if you write a really crazy
|
|
method.
|
|
|
|
== Configuration
|
|
|
|
Run once, at startup, in any environment:
|
|
|
|
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
|
|
|
|
Run only when the environment (RACK_ENV environment variable) is set to
|
|
<tt>:production</tt>:
|
|
|
|
configure :production do
|
|
...
|
|
end
|
|
|
|
Run when the environment is set to either <tt>:production</tt> or
|
|
<tt>:test</tt>:
|
|
|
|
configure :production, :test do
|
|
...
|
|
end
|
|
|
|
You can access those options via <tt>settings</tt>:
|
|
|
|
configure do
|
|
set :foo, 'bar'
|
|
end
|
|
|
|
get '/' do
|
|
settings.foo? # => true
|
|
settings.foo # => 'bar'
|
|
...
|
|
end
|
|
|
|
=== Configuring attack protection
|
|
|
|
Sinatra is using
|
|
{Rack::Protection}[https://github.com/rkh/rack-protection#readme] to defend
|
|
you application against common, opportunistic attacks. You can easily disable
|
|
this behavior (which should result in performance gains):
|
|
|
|
disable :protection
|
|
|
|
To skip a single defense layer, set +protection+ to an options hash:
|
|
|
|
set :protection, :except => :path_traversal
|
|
|
|
You can also hand in an array in order to disable a list of protections:
|
|
|
|
set :protection, :except => [:path_traversal, :session_hijacking]
|
|
|
|
=== Available Settings
|
|
|
|
[absolute_redirects] If disabled, Sinatra will allow relative redirects,
|
|
however, Sinatra will no longer conform with RFC 2616
|
|
(HTTP 1.1), which only allows absolute redirects.
|
|
|
|
Enable if your app is running behind a reverse proxy that
|
|
has not been set up properly. Note that the +url+ helper
|
|
will still produce absolute URLs, unless you pass in
|
|
+false+ as second parameter.
|
|
|
|
Disabled per default.
|
|
|
|
[add_charsets] mime types the <tt>content_type</tt> helper will
|
|
automatically add the charset info to.
|
|
|
|
You should add to it rather than overriding this option:
|
|
|
|
settings.add_charsets << "application/foobar"
|
|
|
|
[app_file] Path to the main application file, used to detect project
|
|
root, views and public folder and inline templates.
|
|
|
|
[bind] IP address to bind to (default: 0.0.0.0).
|
|
Only used for built-in server.
|
|
|
|
[default_encoding] encoding to assume if unknown
|
|
(defaults to <tt>"utf-8"</tt>).
|
|
|
|
[dump_errors] display errors in the log.
|
|
|
|
[environment] current environment, defaults to <tt>ENV['RACK_ENV']</tt>,
|
|
or <tt>"development"</tt> if not available.
|
|
|
|
[logging] use the logger.
|
|
|
|
[lock] Places a lock around every request, only running
|
|
processing on request per Ruby process concurrently.
|
|
|
|
Enabled if your app is not thread-safe.
|
|
Disabled per default.
|
|
|
|
[method_override] use <tt>_method</tt> magic to allow put/delete forms in
|
|
browsers that don't support it.
|
|
|
|
[port] Port to listen on. Only used for built-in server.
|
|
|
|
[prefixed_redirects] Whether or not to insert <tt>request.script_name</tt>
|
|
into redirects if no absolute path is given. That way
|
|
<tt>redirect '/foo'</tt> would behave like
|
|
<tt>redirect to('/foo')</tt>. Disabled per default.
|
|
|
|
[protection] Whether or not to enable web attack protections. See
|
|
protection section above.
|
|
|
|
[public_folder] Path to the folder public files are served from. Only
|
|
used if static file serving is enabled (see
|
|
<tt>static</tt> setting below). Inferred from
|
|
<tt>app_file</tt> setting if not set.
|
|
|
|
[reload_templates] whether or not to reload templates between requests.
|
|
Enabled in development mode.
|
|
|
|
[root] Path to project root folder. Inferred from +app_file+
|
|
setting if not set.
|
|
|
|
[raise_errors] raise exceptions (will stop application). Enabled
|
|
by default when <tt>environment</tt> is set to
|
|
<tt>"test"</tt>, disabled otherwise.
|
|
|
|
[run] if enabled, Sinatra will handle starting the web server,
|
|
do not enable if using rackup or other means.
|
|
|
|
[running] is the built-in server running now?
|
|
do not change this setting!
|
|
|
|
[server] server or list of servers to use for built-in server.
|
|
defaults to ['thin', 'mongrel', 'webrick'], order
|
|
indicates priority.
|
|
|
|
[sessions] enable cookie based sessions support using
|
|
<tt>Rack::Session::Cookie</tt>. See 'Using Sessions'
|
|
section for more information.
|
|
|
|
[show_exceptions] show a stack trace in the browser when an exception
|
|
happens. Enabled by default when <tt>environment</tt>
|
|
is set to <tt>"development"</tt>, disabled otherwise.
|
|
|
|
[static] Whether Sinatra should handle serving static files.
|
|
Disable when using a Server able to do this on its own.
|
|
Disabling will boost performance.
|
|
Enabled per default in classic style, disabled for
|
|
modular apps.
|
|
|
|
[static_cache_control] When Sinatra is serving static files, set this to add
|
|
<tt>Cache-Control</tt> headers to the responses. Uses the
|
|
+cache_control+ helper. Disabled by default.
|
|
Use an explicit array when setting multiple values:
|
|
<tt>set :static_cache_control, [:public, :max_age => 300]</tt>
|
|
|
|
[threaded] If set to +true+, will tell Thin to use
|
|
<tt>EventMachine.defer</tt> for processing the request.
|
|
|
|
[views] Path to the views folder. Inferred from <tt>app_file</tt>
|
|
setting if not set.
|
|
|
|
== Environments
|
|
|
|
There are three predefined +environments+: <tt>"development"</tt>,
|
|
<tt>"production"</tt> and <tt>"test"</tt>. Environments can be set
|
|
through the +RACK_ENV+ environment variable. The default value is
|
|
<tt>"development"</tt>. In this mode, all templates are reloaded between
|
|
requests. Special <tt>not_found</tt> and <tt>error</tt> handlers are installed
|
|
for this environment so you will see a stack trace in your browser.
|
|
In <tt>"production"</tt> and <tt>"test"</tt> templates are cached by default.
|
|
|
|
To run different environments use the <tt>-e</tt> option:
|
|
|
|
ruby my_app.rb -e [ENVIRONMENT]
|
|
|
|
You can use predefined methods: +development?+, +test?+ and +production?+ to
|
|
check which enviroment is currently set.
|
|
|
|
== Error Handling
|
|
|
|
Error handlers run within the same context as routes and before filters, which
|
|
means you get all the goodies it has to offer, like <tt>haml</tt>,
|
|
<tt>erb</tt>, <tt>halt</tt>, etc.
|
|
|
|
=== Not Found
|
|
|
|
When a <tt>Sinatra::NotFound</tt> exception is raised, or the response's status
|
|
code is 404, the <tt>not_found</tt> handler is invoked:
|
|
|
|
not_found do
|
|
'This is nowhere to be found.'
|
|
end
|
|
|
|
=== Error
|
|
|
|
The +error+ handler is invoked any time an exception is raised from a route
|
|
block or a filter. The exception object can be obtained from the
|
|
<tt>sinatra.error</tt> Rack variable:
|
|
|
|
error do
|
|
'Sorry there was a nasty error - ' + env['sinatra.error'].name
|
|
end
|
|
|
|
Custom errors:
|
|
|
|
error MyCustomError do
|
|
'So what happened was...' + env['sinatra.error'].message
|
|
end
|
|
|
|
Then, if this happens:
|
|
|
|
get '/' do
|
|
raise MyCustomError, 'something bad'
|
|
end
|
|
|
|
You get this:
|
|
|
|
So what happened was... something bad
|
|
|
|
Alternatively, you can install an error handler for a status code:
|
|
|
|
error 403 do
|
|
'Access forbidden'
|
|
end
|
|
|
|
get '/secret' do
|
|
403
|
|
end
|
|
|
|
Or a range:
|
|
|
|
error 400..510 do
|
|
'Boom'
|
|
end
|
|
|
|
Sinatra installs special <tt>not_found</tt> and <tt>error</tt> handlers when
|
|
running under the development environment.
|
|
|
|
== Rack Middleware
|
|
|
|
Sinatra rides on Rack[http://rack.rubyforge.org/], a minimal standard
|
|
interface for Ruby web frameworks. One of Rack's most interesting capabilities
|
|
for application developers is support for "middleware" -- components that sit
|
|
between the server and your application monitoring and/or manipulating the
|
|
HTTP request/response to provide various types of common functionality.
|
|
|
|
Sinatra makes building Rack middleware pipelines a cinch via a top-level
|
|
+use+ method:
|
|
|
|
require 'sinatra'
|
|
require 'my_custom_middleware'
|
|
|
|
use Rack::Lint
|
|
use MyCustomMiddleware
|
|
|
|
get '/hello' do
|
|
'Hello World'
|
|
end
|
|
|
|
The semantics of +use+ are identical to those defined for the
|
|
Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] DSL
|
|
(most frequently used from rackup files). For example, the +use+ method
|
|
accepts multiple/variable args as well as blocks:
|
|
|
|
use Rack::Auth::Basic do |username, password|
|
|
username == 'admin' && password == 'secret'
|
|
end
|
|
|
|
Rack is distributed with a variety of standard middleware for logging,
|
|
debugging, URL routing, authentication, and session handling. Sinatra uses
|
|
many of these components automatically based on configuration so you
|
|
typically don't have to +use+ them explicitly.
|
|
|
|
You can find useful middleware in
|
|
{rack}[https://github.com/rack/rack/tree/master/lib/rack],
|
|
{rack-contrib}[https://github.com/rack/rack-contrib#readme],
|
|
with {CodeRack}[http://coderack.org/] or in the
|
|
{Rack wiki}[https://github.com/rack/rack/wiki/List-of-Middleware].
|
|
|
|
== Testing
|
|
|
|
Sinatra tests can be written using any Rack-based testing library or framework.
|
|
{Rack::Test}[http://rdoc.info/github/brynary/rack-test/master/frames]
|
|
is recommended:
|
|
|
|
require 'my_sinatra_app'
|
|
require 'test/unit'
|
|
require 'rack/test'
|
|
|
|
class MyAppTest < Test::Unit::TestCase
|
|
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_rack_env
|
|
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
|
|
assert_equal "You're using Songbird!", last_response.body
|
|
end
|
|
end
|
|
|
|
== Sinatra::Base - Middleware, Libraries, and Modular Apps
|
|
|
|
Defining your app at the top-level works well for micro-apps but has
|
|
considerable drawbacks when building reusable components such as Rack
|
|
middleware, Rails metal, simple libraries with a server component, or even
|
|
Sinatra extensions. The top-level assumes a micro-app style configuration
|
|
(e.g., a single application file, <tt>./public</tt> and <tt>./views</tt>
|
|
directories, logging, exception detail page, etc.). That's where
|
|
<tt>Sinatra::Base</tt> comes into play:
|
|
|
|
require 'sinatra/base'
|
|
|
|
class MyApp < Sinatra::Base
|
|
set :sessions, true
|
|
set :foo, 'bar'
|
|
|
|
get '/' do
|
|
'Hello world!'
|
|
end
|
|
end
|
|
|
|
The methods available to <tt>Sinatra::Base</tt> subclasses are exactly as those
|
|
available via the top-level DSL. Most top-level apps can be converted to
|
|
<tt>Sinatra::Base</tt> components with two modifications:
|
|
|
|
* Your file should require <tt>sinatra/base</tt> instead of +sinatra+;
|
|
otherwise, all of Sinatra's DSL methods are imported into the main
|
|
namespace.
|
|
* Put your app's routes, error handlers, filters, and options in a subclass
|
|
of <tt>Sinatra::Base</tt>.
|
|
|
|
<tt>Sinatra::Base</tt> is a blank slate. Most options are disabled by default,
|
|
including the built-in server. See
|
|
{Options and Configuration}[http://sinatra.github.com/configuration.html]
|
|
for details on available options and their behavior.
|
|
|
|
=== Modular vs. Classic Style
|
|
|
|
Contrary to common belief, there is nothing wrong with classic style. If it
|
|
suits your application, you do not have to switch to a modular application.
|
|
|
|
The main downsides of using classic style rather than modular style is that
|
|
you may only have one Sinatra application per Ruby process. If you plan to use
|
|
more than one, switch to modular style. There is no reason you cannot mix
|
|
modular and classic style.
|
|
|
|
If switching from one style to the other, you should be aware of slightly
|
|
different default settings:
|
|
|
|
Setting Classic Modular
|
|
|
|
app_file file loading sinatra file subclassing Sinatra::Base
|
|
run $0 == app_file false
|
|
logging true false
|
|
method_override true false
|
|
inline_templates true false
|
|
static true false
|
|
|
|
|
|
=== Serving a Modular Application
|
|
|
|
There are two common options for starting a modular app, actively starting with
|
|
<tt>run!</tt>:
|
|
|
|
# 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
|
|
|
|
Start with:
|
|
|
|
ruby my_app.rb
|
|
|
|
Or with a <tt>config.ru</tt>, which allows using any Rack handler:
|
|
|
|
# config.ru
|
|
require './my_app'
|
|
run MyApp
|
|
|
|
Run:
|
|
|
|
rackup -p 4567
|
|
|
|
=== Using a Classic Style Application with a config.ru
|
|
|
|
Write your app file:
|
|
|
|
# app.rb
|
|
require 'sinatra'
|
|
|
|
get '/' do
|
|
'Hello world!'
|
|
end
|
|
|
|
And a corresponding <tt>config.ru</tt>:
|
|
|
|
require './app'
|
|
run Sinatra::Application
|
|
|
|
=== When to use a config.ru?
|
|
|
|
Good signs you probably want to use a <tt>config.ru</tt>:
|
|
|
|
* You want to deploy with a different Rack handler (Passenger, Unicorn,
|
|
Heroku, ...).
|
|
* You want to use more than one subclass of <tt>Sinatra::Base</tt>.
|
|
* You want to use Sinatra only for middleware, but not as endpoint.
|
|
|
|
<b>There is no need to switch to a <tt>config.ru</tt> only because you
|
|
switched to modular style, and you don't have to use modular style for running
|
|
with a <tt>config.ru</tt>.</b>
|
|
|
|
=== Using Sinatra as Middleware
|
|
|
|
Not only is Sinatra able to use other Rack middleware, any Sinatra application
|
|
can in turn be added in front of any Rack endpoint as middleware itself. This
|
|
endpoint could be another Sinatra application, or any other Rack-based
|
|
application (Rails/Ramaze/Camping/...):
|
|
|
|
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
|
|
|
|
=== Dynamic Application Creation
|
|
|
|
Sometimes you want to create new applications at runtime without having to
|
|
assign them to a constant, you can do this with <tt>Sinatra.new</tt>:
|
|
|
|
require 'sinatra/base'
|
|
my_app = Sinatra.new { get('/') { "hi" } }
|
|
my_app.run!
|
|
|
|
It takes the application to inherit from as optional argument:
|
|
|
|
# config.ru
|
|
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
|
|
|
|
This is especially useful for testing Sinatra extensions or using Sinatra in
|
|
your own library.
|
|
|
|
This also makes using Sinatra as middleware extremely easy:
|
|
|
|
require 'sinatra/base'
|
|
|
|
use Sinatra do
|
|
get('/') { ... }
|
|
end
|
|
|
|
run RailsProject::Application
|
|
|
|
== Scopes and Binding
|
|
|
|
The scope you are currently in determines what methods and variables are
|
|
available.
|
|
|
|
=== Application/Class Scope
|
|
|
|
Every Sinatra application corresponds to a subclass of <tt>Sinatra::Base</tt>.
|
|
If you are using the top-level DSL (<tt>require 'sinatra'</tt>), then this
|
|
class is <tt>Sinatra::Application</tt>, otherwise it is the subclass you
|
|
created explicitly. At class level you have methods like +get+ or +before+, but
|
|
you cannot access the +request+ object or the +session+, as there only is a
|
|
single application class for all requests.
|
|
|
|
Options created via +set+ are methods at class level:
|
|
|
|
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
|
|
|
|
You have the application scope binding inside:
|
|
|
|
* Your application class body
|
|
* Methods defined by extensions
|
|
* The block passed to +helpers+
|
|
* Procs/blocks used as value for +set+
|
|
* The block passed to <tt>Sinatra.new</tt>
|
|
|
|
You can reach the scope object (the class) like this:
|
|
|
|
* Via the object passed to configure blocks (<tt>configure { |c| ... }</tt>)
|
|
* +settings+ from within request scope
|
|
|
|
=== Request/Instance Scope
|
|
|
|
For every incoming request, a new instance of your application class is
|
|
created and all handler blocks run in that scope. From within this scope you
|
|
can access the +request+ and +session+ object or call rendering methods like
|
|
+erb+ or +haml+. You can access the application scope from within the request
|
|
scope via the +settings+ helper:
|
|
|
|
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
|
|
|
|
You have the request scope binding inside:
|
|
|
|
* get/head/post/put/delete/options blocks
|
|
* before/after filters
|
|
* helper methods
|
|
* templates/views
|
|
|
|
=== Delegation Scope
|
|
|
|
The delegation scope just forwards methods to the class scope. However, it
|
|
does not behave 100% like the class scope, as you do not have the class
|
|
binding. Only methods explicitly marked for delegation are available and you
|
|
do not share variables/state with the class scope (read: you have a different
|
|
+self+). You can explicitly add method delegations by calling
|
|
<tt>Sinatra::Delegator.delegate :method_name</tt>.
|
|
|
|
You have the delegate scope binding inside:
|
|
|
|
* The top level binding, if you did <tt>require "sinatra"</tt>
|
|
* An object extended with the <tt>Sinatra::Delegator</tt> mixin
|
|
|
|
Have a look at the code for yourself: here's the
|
|
{Sinatra::Delegator mixin}[https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633]
|
|
being {extending the main object}[https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30].
|
|
|
|
== Command Line
|
|
|
|
Sinatra applications can be run directly:
|
|
|
|
ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
|
|
|
|
Options are:
|
|
|
|
-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 thin)
|
|
-x # turn on the mutex lock (default is off)
|
|
|
|
== Requirement
|
|
|
|
The following Ruby versions are officially supported:
|
|
|
|
[ Ruby 1.8.7 ]
|
|
1.8.7 is fully supported, however, if nothing is keeping you from it, we
|
|
recommend upgrading to 1.9.2 or switching to JRuby or Rubinius. Support for
|
|
1.8.7 will not be dropped before Sinatra 2.0 and Ruby 2.0 except maybe for
|
|
the unlikely event of 1.8.8 being released. Even then, we might continue
|
|
supporting it. <b>Ruby 1.8.6 is no longer supported.</b> If you want to run
|
|
with 1.8.6, downgrade to Sinatra 1.2, which will receive bug fixes until
|
|
Sinatra 1.4.0 is released.
|
|
|
|
[ Ruby 1.9.2 ]
|
|
1.9.2 is fully supported and recommended. Note that Radius and Markaby
|
|
are currently not 1.9 compatible. Do not use 1.9.2p0, it is known to cause
|
|
segmentation faults when running Sinatra. Support will continue at least
|
|
until the release of Ruby 1.9.4/2.0 and support for the latest 1.9 release
|
|
will continue as long as it is still supported by the Ruby core team.
|
|
|
|
[ Ruby 1.9.3 ]
|
|
1.9.3 is fully supported. We recommend waiting for higher patch levels to be
|
|
released (current one is p0) before using it in production. Please note that
|
|
switching to 1.9.3 from an earlier version will invalidate all sessions.
|
|
|
|
[ Rubinius ]
|
|
Rubinius is officially supported (Rubinius >= 1.2.4), everything, including
|
|
all template languages, works. The upcoming 2.0 release is supported as
|
|
well.
|
|
|
|
[ JRuby ]
|
|
JRuby is officially supported (JRuby >= 1.6.5). No issues with third party
|
|
template libraries are known, however, if you choose to use JRuby, please
|
|
look into JRuby rack handlers, as the Thin web server is not fully supported
|
|
on JRuby. JRuby's support for C extensions is still experimental, which only
|
|
affects RDiscount, Redcarpet and RedCloth at the moment.
|
|
|
|
We also keep an eye on upcoming Ruby versions.
|
|
|
|
The following Ruby implementations are not officially supported but still are
|
|
known to run Sinatra:
|
|
|
|
* Older versions of JRuby and Rubinius
|
|
* Ruby Enterprise Edition
|
|
* MacRuby, Maglev, IronRuby
|
|
* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those)
|
|
|
|
Not being officially supported means if things only break there and not on a
|
|
supported platform, we assume it's not our issue but theirs.
|
|
|
|
We also run our CI against ruby-head (the upcoming 2.0.0) and the 1.9.4
|
|
branch, but we can't guarantee anything, since it is constantly moving. Expect
|
|
both 1.9.4p0 and 2.0.0p0 to be supported.
|
|
|
|
Sinatra should work on any operating system supported by the chosen Ruby
|
|
implementation.
|
|
|
|
You will not be able to run Sinatra on Cardinal, SmallRuby, BlueRuby or any
|
|
Ruby version prior to 1.8.7 as of the time being.
|
|
|
|
== The Bleeding Edge
|
|
|
|
If you would like to use Sinatra's latest bleeding code, feel free to run your
|
|
application against the master branch, it should be rather stable.
|
|
|
|
We also push out prerelease gems from time to time, so you can do a
|
|
|
|
gem install sinatra --pre
|
|
|
|
To get some of the latest features.
|
|
|
|
=== With Bundler
|
|
|
|
If you want to run your application with the latest Sinatra, using
|
|
{Bundler}[http://gembundler.com/] is the recommended way.
|
|
|
|
First, install bundler, if you haven't:
|
|
|
|
gem install bundler
|
|
|
|
Then, in your project directory, create a +Gemfile+:
|
|
|
|
source :rubygems
|
|
gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
|
|
|
|
# other dependencies
|
|
gem 'haml' # for instance, if you use haml
|
|
gem 'activerecord', '~> 3.0' # maybe you also need ActiveRecord 3.x
|
|
|
|
Note that you will have to list all your applications dependencies in there.
|
|
Sinatra's direct dependencies (Rack and Tilt) will, however, be automatically
|
|
fetched and added by Bundler.
|
|
|
|
Now you can run your app like this:
|
|
|
|
bundle exec ruby myapp.rb
|
|
|
|
=== Roll Your Own
|
|
|
|
Create a local clone and run your app with the <tt>sinatra/lib</tt> directory
|
|
on the <tt>$LOAD_PATH</tt>:
|
|
|
|
cd myapp
|
|
git clone git://github.com/sinatra/sinatra.git
|
|
ruby -Isinatra/lib myapp.rb
|
|
|
|
To update the Sinatra sources in the future:
|
|
|
|
cd myapp/sinatra
|
|
git pull
|
|
|
|
=== Install Globally
|
|
|
|
You can build the gem on your own:
|
|
|
|
git clone git://github.com/sinatra/sinatra.git
|
|
cd sinatra
|
|
rake sinatra.gemspec
|
|
rake install
|
|
|
|
If you install gems as root, the last step should be
|
|
|
|
sudo rake install
|
|
|
|
== Versioning
|
|
|
|
Sinatra follows {Semantic Versioning}[http://semver.org/], both SemVer and
|
|
SemVerTag.
|
|
|
|
== Further Reading
|
|
|
|
* {Project Website}[http://www.sinatrarb.com/] - Additional documentation,
|
|
news, and links to other resources.
|
|
* {Contributing}[http://www.sinatrarb.com/contributing] - Find a bug? Need
|
|
help? Have a patch?
|
|
* {Issue tracker}[http://github.com/sinatra/sinatra/issues]
|
|
* {Twitter}[http://twitter.com/sinatra]
|
|
* {Mailing List}[http://groups.google.com/group/sinatrarb/topics]
|
|
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
|
|
* {Sinatra Book}[http://sinatra-book.gittr.com] Cookbook Tutorial
|
|
* {Sinatra Recipes}[http://recipes.sinatrarb.com/] Community
|
|
contributed recipes
|
|
* API documentation for the {latest release}[http://rubydoc.info/gems/sinatra]
|
|
or the {current HEAD}[http://rubydoc.info/github/sinatra/sinatra] on
|
|
http://rubydoc.info
|
|
* {CI server}[http://ci.rkh.im/view/Sinatra/]
|