- Sinatra
- Маршруты
- Статические файлы
- Представления / Шаблоны
- Буквальные шаблоны
- Доступные шаблонизаторы
- Haml шаблоны
- Erb шаблоны
- Builder шаблоны
- Nokogiri шаблоны
- Sass шаблоны
- SCSS шаблоны
- Less шаблоны
- Liquid шаблоны
- Markdown шаблоны
- Textile шаблоны
- RDoc шаблоны
- AsciiDoc шаблоны
- Radius шаблоны
- Markaby шаблоны
- RABL шаблоны
- Slim шаблоны
- Creole шаблоны
- MediaWiki шаблоны
- CoffeeScript шаблоны
- Stylus шаблоны
- Yajl шаблоны
- WLang шаблоны
- Доступ к переменным в шаблонах
- Шаблоны с
yield
и вложенные раскладки (layout) - Включённые шаблоны
- Именованные шаблоны
- Привязка файловых расширений
- Добавление собственного движка рендеринга
- Фильтры
- Методы-помощники
- Использование сессий
- Прерывание
- Передача
- Вызов другого маршрута
- Задание тела, кода и заголовков ответа
- Стриминг ответов
- Логирование
- Mime-типы
- Генерирование URL
- Перенаправление (редирект)
- Управление кэшированием
- Отправка файлов
- Доступ к объекту запроса
- Вложения
- Работа со временем и датами
- Поиск шаблонов
- Конфигурация
- Режим, окружение
- Обработка ошибок
- Rack "прослойки"
- Тестирование
- Sinatra::Base — "прослойки", библиотеки и модульные приложения
- Области видимости и привязка
- Командная строка
- Системные требования
- На острие
- Версии
- Дальнейшее чтение
Внимание: Этот документ является переводом английской версии и может быть устаревшим
Sinatra — это предметно-ориентированный каркас (DSL) для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий:
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
Установите gem:
gem install sinatra
и запустите приложение с помощью:
ruby myapp.rb
Оцените результат: http://localhost:4567
Рекомендуется также установить Thin, сделать это можно командой: gem install thin
. Thin — это более производительный и функциональный сервер для
разработки приложений на Sinatra.
В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут связан с блоком кода:
get '/' do
# .. что-то показать ..
end
post '/' do
# .. что-то создать ..
end
put '/' do
# .. что-то заменить ..
end
patch '/' do
# .. что-то изменить ..
end
delete '/' do
# .. что-то удалить ..
end
options '/' do
# .. что-то ответить ..
end
link '/' do
.. что-то подключить ..
end
unlink '/' do
.. что-то отключить ..
end
Маршруты сверяются с запросом в порядке очередности их записи в файле приложения. Первый же совпавший с запросом маршрут и будет вызван.
Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше
params
:
get '/hello/:name' do
# соответствует "GET /hello/foo" и "GET /hello/bar",
# где params['name'] 'foo' или 'bar'
"Hello #{params['name']}!"
end
Также можно использовать именованные параметры в качестве переменных блока:
get '/hello/:name' do |n|
"Hello #{n}!"
end
Шаблоны маршрутов также могут включать в себя splat (или '*' маску,
обозначающую любой символ) параметры, доступные в массиве params['splat']
:
get '/say/*/to/*' do
# соответствует /say/hello/to/world
params['splat'] # => ["hello", "world"]
end
get '/download/*.*' do
# соответствует /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 /meta/hello/world", "GET /hello/world/1234" и так далее
get %r{/hello/([\w]+)} do |c|
"Hello, #{c}!"
end
Шаблоны маршрутов могут иметь необязательные параметры:
get '/posts/:format?' do
# соответствует "GET /posts/", "GET /posts/json", "GET /posts/xml" и т.д.
end
Кстати, если вы не отключите защиту от обратного пути в директориях (path traversal, см. ниже), путь запроса может быть изменен до начала поиска подходящего маршрута.
Маршруты могут включать различные условия совпадений, например, клиентское приложение (user agent):
get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
"You're using Songbird version #{params['agent'][0]}"
end
get '/foo' do
# соответствует не-songbird браузерам
end
Другими доступными условиями являются host_name
и 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
Вы можете задать собственные условия:
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
Для условия, которое принимает несколько параметров, используйте звездочку:
set(:auth) do |*roles| # <- обратите внимание на звездочку
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 клиенту, или следующей "прослойкой" (middleware) в Rack стеке. Чаще всего это строка, как в примерах выше. Но также приемлемы и другие значения.
Вы можете вернуть любой объект, который будет либо корректным Rack ответом, объектом Rack body, либо кодом состояния HTTP:
- массив с тремя переменными:
[код (Fixnum), заголовки (Hash), тело ответа (должно отвечать на #each)]
; - массив с двумя переменными:
[код (Fixnum), тело ответа (должно отвечать на #each)]
; - объект, отвечающий на
#each
, который передает только строковые типы данных в этот блок; - Fixnum, представляющий код состояния HTTP.
Таким образом, легко можно реализовать, например, поточный пример:
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
Вы также можете использовать метод stream
(описываемый ниже), чтобы
уменьшить количество дублируемого кода и держать логику стриминга прямо в
маршруте.
Как показано выше, Sinatra поставляется со встроенной поддержкой строк и регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете легко определить свои собственные детекторы совпадений (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
Заметьте, что предыдущий пример, возможно, чересчур усложнен, потому что он может быть реализован так:
get // do
pass if request.path_info == "/index"
# ...
end
Или с использованием негативного просмотра вперед:
get %r{(?!/index)} do
# ...
end
Статические файлы отдаются из ./public
директории. Вы можете указать другое
место, используя опцию :public_folder
:
set :public_folder, File.dirname(__FILE__) + '/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/index.erb
, вложенным в views/post.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.
- views
- Директория с шаблонами. По умолчанию: settings.views.
- layout
- Использовать или нет лэйаут (true или false). Если же значение Symbol, то указывает, какой шаблон использовать в качестве лэйаута. Например: erb :index, :layout => !request.xhr?
- content_type
- Content-Type отображенного шаблона. По умолчанию: задается шаблонизатором.
- scope
- Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр приложения. Если вы измените эту опцию, то переменные экземпляра и методы-помощники станут недоступными в ваших шаблонах.
- layout_engine
- Шаблонизатор, который следует использовать для отображения лэйаута. Полезная опция для шаблонизаторов, в которых нет никакой поддержки лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого шаблона. Пример: set :rdoc, :layout_engine => :erb
По умолчанию считается, что шаблоны находятся в директории ./views
. Чтобы
использовать другую директорию с шаблонами:
set :views, settings.root + '/templates'
Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов
(Symbol), даже когда они в поддиректории (в этом случае используйте
:'subdir/template'
). Вы должны использовать символы, потому что иначе
шаблонизаторы попросту отображают любые строки, переданные им.
get '/' do
haml '%div.title Hello World'
end
Отобразит шаблон, переданный строкой.
Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую реализацию использовать, вам следует просто подключить нужную библиотеку:
require 'rdiscount' # или require 'bluecloth'
get('/') { markdown :index }
Зависимости | haml |
Расширения файлов | .haml |
Пример | haml :index, :format => :html5 |
Зависимости | erubis или erb (включен в Ruby) |
Расширения файлов | .erb, .rhtml or .erubis (только Erubis) |
Пример | erb :index |
Зависимости | builder |
Расширения файлов | .builder |
Пример | builder { |xml| xml.em "hi" } |
Блок также используется и для встроенных шаблонов (см. пример).
Зависимости | nokogiri |
Расширения файлов | .nokogiri |
Пример | nokogiri { |xml| xml.em "hi" } |
Блок также используется и для встроенных шаблонов (см. пример).
Зависимости | sass |
Расширения файлов | .sass |
Пример | sass :stylesheet, :style => :expanded |
Зависимости | sass |
Расширения файлов | .scss |
Пример | scss :stylesheet, :style => :expanded |
Зависимости | less |
Расширения файлов | .less |
Пример | less :stylesheet |
Зависимости | liquid |
Расширения файлов | .liquid |
Пример | liquid :index, :locals => { :key => 'value' } |
Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме yield
), то
вы почти всегда будете передавать в шаблон локальные переменные.
Зависимости | Любая из библиотек: RDiscount, RedCarpet, BlueCloth, kramdown, maruku |
Расширения файлов | .markdown, .mkd and .md |
Пример | markdown :index, :layout_engine => :erb |
В Markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:
erb :overview, :locals => { :text => markdown(:introduction) }
Заметьте, что вы можете вызывать метод markdown
из других шаблонов:
%h1 Hello From Haml!
%p= markdown(:greetings)
Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете
использовать лэйауты на Markdown. Тем не менее, есть возможность использовать
один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
опции :layout_engine
.
Зависимости | RedCloth |
Расширения файлов | .textile |
Пример | textile :index, :layout_engine => :erb |
В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:
erb :overview, :locals => { :text => textile(:introduction) }
Заметьте, что вы можете вызывать метод textile
из других шаблонов:
%h1 Hello From Haml!
%p= textile(:greetings)
Вы не можете вызывать Ruby из Textile, соответственно, вы не можете
использовать лэйауты на Textile. Тем не менее, есть возможность использовать
один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
опции :layout_engine
.
Зависимости | RDoc |
Расширения файлов | .rdoc |
Пример | rdoc :README, :layout_engine => :erb |
В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:
erb :overview, :locals => { :text => rdoc(:introduction) }
Заметьте, что вы можете вызывать метод rdoc
из других шаблонов:
%h1 Hello From Haml!
%p= rdoc(:greetings)
Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать
лэйауты на RDoc. Тем не менее, есть возможность использовать один шаблонизатор
для отображения шаблона, а другой для лэйаута с помощью опции
:layout_engine
.
Зависимости | Asciidoctor |
Расширения файлов | .asciidoc, .adoc и .ad |
Пример | asciidoc :README, :layout_engine => :erb |
Так как в AsciiDoc шаблонах невозможно вызывать методы из Ruby напрямую, то вы почти всегда будете передавать в шаблон локальные переменные.
Зависимости | Radius |
Расширения файлов | .radius |
Пример | radius :index, :locals => { :key => 'value' } |
Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы почти всегда будете передавать в шаблон локальные переменные.
Зависимости | Markaby |
Расширения файлов | .mab |
Пример | markaby { h1 "Welcome!" } |
Блок также используется и для встроенных шаблонов (см. пример).
Зависимости | Rabl |
Расширения файлов | .rabl |
Пример | rabl :index |
Зависимости | Slim Lang |
Расширения файлов | .slim |
Пример | slim :index |
Зависимости | Creole |
Расширения файлов | .creole |
Пример | creole :wiki, :layout_engine => :erb |
В Creole невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:
erb :overview, :locals => { :text => creole(:introduction) }
Заметьте, что вы можете вызывать метод creole
из других шаблонов:
%h1 Hello From Haml!
%p= creole(:greetings)
Вы не можете вызывать Ruby из Creole, соответственно, вы не можете
использовать лэйауты на Creole. Тем не менее, есть возможность использовать
один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
опции :layout_engine
.
Зависимости | WikiCloth |
Расширения файлов | .mediawiki и .mw |
Пример | mediawiki :wiki, :layout_engine => :erb |
В разметке MediaWiki невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:
erb :overview, :locals => { :text => mediawiki(:introduction) }
Заметьте, что вы можете вызывать метод mediawiki
из других шаблонов:
%h1 Hello From Haml!
%p= mediawiki(:greetings)
Вы не можете вызывать Ruby из MediaWiki, соответственно, вы не можете
использовать лэйауты на MediaWiki. Тем не менее, есть возможность использовать
один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
опции :layout_engine
.
Зависимости | CoffeeScript и способ запускать JavaScript |
Расширения файлов | .coffee |
Пример | coffee :index |
Зависимости | Stylus и способ запускать JavaScript |
Расширение файла | .styl |
Пример | stylus :index |
Перед тем, как использовать шаблоны стилус, загрузите stylus
и
stylus/tilt
:
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :example
end
Зависимости | 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);
Зависимости | wlang |
Расширения файлов | .wlang |
Пример | wlang :index, :locals => { :key => 'value' } |
Так как в WLang шаблонах невозможно вызывать методы из Ruby напрямую (за
исключением yield
), то вы почти всегда будете передавать в шаблон локальные
переменные.
Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра, установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах:
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
Это обычный подход, когда шаблоны рендерятся как части других шаблонов.
Раскладка (layout) обычно представляет собой шаблон, который исполняет
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
В настоящее время, следующие интерпретирующие шаблоны методы
принимают блок:
erb
, haml
, liquid
, slim
, wlang
.
Общий метод заполнения шаблонов 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
для
шаблонов Textile:
Tilt.register :tt, Tilt[:textile]
Сначала зарегистрируйте свой движок в Tilt, а затем создайте метод, отвечающий за рендеринг:
Tilt.register :myat, MyAwesomeTemplateEngine
helpers do
def myat(*args) render(:myat, *args) end
end
get '/' do
myat :index
end
Отобразит ./views/index.myat
. Чтобы узнать больше о Tilt, смотрите
https://github.com/rtomayko/tilt
before
-фильтры выполняются перед каждым запросом в том же контексте, что и
маршруты, и могут изменять как запрос, так и ответ на него. Переменные
экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах:
before do
@note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
@note #=> 'Hi!'
params['splat'] #=> 'bar/baz'
end
after
-фильтры выполняются после каждого запроса в том же контексте
и могут изменять как запрос, так и ответ на него. Переменные
экземпляра, установленные в before
-фильтрах и маршрутах, будут доступны в
after
-фильтрах:
after do
puts response.status
end
Заметьте: если вы используете метод body
, а не просто возвращаете строку из
маршрута, то тело ответа не будет доступно в after
-фильтрах, так как оно
будет сгенерировано позднее.
Фильтры могут использовать шаблоны URL и будут интерпретированы, только если путь запроса совпадет с этим шаблоном:
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
Для повышения безопасности данные сессии в куках подписываются секретным ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ будет меняться с каждым запуском приложения, вы, возможно, захотите установить ключ вручную, чтобы у всех экземпляров вашего приложения был один и тот же ключ:
set :session_secret, 'super secret'
Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш
опций в параметр sessions
:
set :sessions, :domain => 'foo.com'
Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах foo.com, добавьте . перед доменом:
set :sessions, :domain => '.foo.com'
Заметьте, что при использовании enable :sessions
все данные сохраняются в
куках (cookies). Это может быть не совсем то, что вы хотите (например,
сохранение больших объемов данных увеличит ваш трафик). В таком случае вы
можете использовать альтернативную Rack "прослойку" (middleware), реализующую
механизм сессий. Для этого используете один из способов ниже:
enable :sessions
set :session_store, Rack::Session::Pool
Или установите параметры сессии с помощью хеша опций:
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
Вы так же можете не вызывать enable :sessions
, а вместо этого вызывать
необходимую вам прослойку так же, как вы это обычно делаете. Очень важно
обратить внимание на то, что когда вы используете этот метод, основной способ
защиты сессии не будет включен по умолчанию. Если вы хотите включить защиту,
вам нужно добавить следующие строчки:
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.
Хорошим тоном является установка кода состояния HTTP и тела ответа в
возвращаемом значении обработчика маршрута. Тем не менее, в некоторых
ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке
потока исполнения. Вы можете сделать это с помощью метода-помощника body
.
Если вы задействуете метод body
, то вы можете использовать его и в
дальнейшем, чтобы получить доступ к телу ответа.
get '/foo' do
body "bar"
end
after do
puts body
end
Также можно передать блок в метод body
, который затем будет вызван
обработчиком Rack (такой подход может быть использован для реализации
поточного ответа, см. "Возвращаемые значения").
Аналогично вы можете установить код ответа и его заголовки:
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
Как и body
, методы headers
и status
, вызванные без аргументов,
возвращают свои текущие значения.
Иногда требуется начать отправлять данные клиенту прямо в процессе
генерирования частей этих данных. В особых случаях требуется постоянно
отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете
использовать метод 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, Server Sent Events, и может служить основой для WebSockets. Также такой подход можно использовать для увеличения производительности в случае, когда какая-то часть контента зависит от медленного ресурса.
Заметьте, что возможности стриминга, особенно количество одновременно
обслуживаемых запросов, очень сильно зависят от используемого веб-сервера.
Некоторые серверы могут и вовсе не поддерживать стриминг. Если сервер не
поддерживает стриминг, то все данные будут отправлены за один раз сразу после
того, как блок, переданный в stream
, завершится. Стриминг вообще не работает
при использовании Shotgun.
Если метод используется с параметром keep_open
, то он не будет вызывать
close
у объекта потока, что позволит вам закрыть его позже в любом другом
месте. Это работает только с событийными серверами, например, с Thin и
Rainbows. Другие же серверы все равно будут закрывать поток:
# long polling
set :server, :thin
connections = []
get '/subscribe' do
# регистрация клиента
stream(:keep_open) do |out|
connections << out }
# удаление "мертвых клиентов"
connections.reject!(&:closed?)
end
end
post '/message' do
connections.each do |out|
# уведомить клиента о новом сообщении
out << params['message'] << "\n"
# указать клиенту на необходимость снова соединиться
out.close
end
# допуск
"message received"
end
В области видимости запроса метод logger
предоставляет доступ к экземпляру
Logger
:
get '/' do
logger.info "loading data"
# ...
end
Этот логер автоматически учитывает ваши настройки логирования в Rack. Если логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы можете смело использовать его в маршрутах и фильтрах.
Заметьте, что логирование включено по умолчанию только для
Sinatra::Application
, а если ваше приложение — подкласс Sinatra::Base
, то
вы, наверное, захотите включить его вручную:
class MyApp < Sinatra::Base
configure :production, :development do
enable :logging
end
end
Чтобы избежать использования любой логирующей "прослойки", задайте опции
logging
значение nil
. Тем не менее, не забывайте, что в такой ситуации
logger
вернет nil
. Чаще всего так делают, когда задают свой собственный
логер. Sinatra будет использовать то, что находится в env['rack.logger']
.
Когда вы используете send_file
или статические файлы, у вас могут быть
mime-типы, которые Sinatra не понимает по умолчанию. Используйте mime_type
для их регистрации по расширению файла:
configure do
mime_type :foo, 'text/foo'
end
Вы также можете использовать это в content_type
методе-помощнике:
get '/' do
content_type :foo
"foo foo foo"
end
Чтобы сформировать URL, вам следует использовать метод url
, например, в Haml:
%a{:href => url('/foo')} foo
Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они присутствуют.
Наряду с url
вы можете использовать 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
Чтобы как следует использовать кэширование, вам следует подумать об
использовании etag
или last_modified
. Рекомендуется использовать эти
методы-помощники до выполнения ресурсоемких вычислений, так как они
немедленно отправят ответ клиенту, если текущая версия уже есть в их кэше:
get '/article/:id' do
@article = Article.find params['id']
last_modified @article.updated_at
etag @article.sha1
erb :article
end
Также вы можете использовать weak 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
Если вы хотите использовать weak 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
- тип файла, по умолчанию: определяется по расширению файла.
- disposition
- используется для заголовка Content-Disposition, возможные значения: nil (по умолчанию), :attachment и :inline.
- length
- значения для заголовка Content-Length, по умолчанию: размер файла.
- status
- Код ответа. Полезно, когда отдается статический файл в качестве страницы с сообщением об ошибке.
Этот метод будет использовать возможности Rack сервера для отправки файлов,
если они доступны, в противном случае будет напрямую отдавать файл из Ruby
процесса. Метод send_file
также обеспечивает автоматическую обработку
частичных (range) запросов с помощью Sinatra.
Объект входящего запроса доступен на уровне обработки запроса (в фильтрах,
маршрутах, обработчиках ошибок) с помощью request
метода:
# приложение запущено на 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.scheme # "http"
request.script_name # "/example"
request.path_info # "/foo"
request.port # 80
request.request_method # "GET"
request.query_string # ""
request.content_length # длина тела запроса
request.media_type # медиатип тела запроса
request.host # "example.com"
request.get? # true (есть аналоги для других методов HTTP)
request.form_data? # false
request["some_param"] # значение параметра some_param. Шорткат для хеша params
request.referrer # источник запроса клиента либо '/'
request.user_agent # user agent (используется для :agent условия)
request.cookies # хеш, содержащий cookies браузера
request.xhr? # является ли запрос ajax запросом?
request.url # "http://example.com/example/foo"
request.path # "/example/foo"
request.ip # IP-адрес клиента
request.secure? # false (true, если запрос сделан через SSL)
request.forwarded? # true (если сервер работает за обратным прокси)
request.env # "сырой" env хеш, полученный Rack
end
Некоторые опции, такие как script_name
или path_info
, доступны для
изменения:
before { request.path_info = "/" }
get "/" do
"all requests end up here"
end
request.body
является IO или StringIO объектом:
post "/api" do
request.body.rewind # в случае, если кто-то уже прочитал тело запроса
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. Он также может конвертировать DateTime
, Date
и
подобные классы:
get '/' do
pass if Time.now > time_for('Dec 23, 2012')
"still time"
end
Этот метод используется внутри Sinatra методами expires
, last_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, :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
Вы можете легко вынести этот код в расширение и поделиться им с остальными!
Заметьте, что find_template
не проверяет, существует ли файл на самом деле,
а вызывает заданный блок для всех возможных путей. Дело тут не в
производительности, дело в том, что render
вызовет break
, как только файл
не будет найден. Содержимое и местонахождение шаблонов будет закэшировано,
если приложение запущено не в режиме разработки (set :environment, :development
). Вы должны помнить об этих нюансах, если пишите по-настоящему
"сумасшедший" метод.
Этот блок исполняется один раз при старте в любом окружении, режиме (environment):
configure do
# задание одной опции
set :option, 'value'
# устанавливаем несколько опций
set :a => 1, :b => 2
# то же самое, что и `set :option, true`
enable :option
# то же самое, что и `set :option, false`
disable :option
# у вас могут быть "динамические" опции с блоками
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, :session_hijacking]
- absolute_redirects
- если отключено, то Sinatra будет позволять использование относительных перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP 1.1), который разрешает только абсолютные перенаправления.
- Включайте эту опцию, если ваше приложение работает за обратным прокси, который настроен не совсем корректно. Обратите внимание, метод url все равно будет генерировать абсолютные URL, если вы не передадите false вторым аргументом.
- Отключено по умолчанию.
- add_charset
- mime-типы, к которым метод content_type будет автоматически добавлять информацию о кодировке. Вам следует добавлять значения к этой опции вместо ее переопределения: settings.add_charset << "application/foobar"
- app_file
- путь к главному файлу приложения, используется для нахождения корневой директории проекта, директорий с шаблонами и статическими файлами, вложенных шаблонов.
- bind
- используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только встроенным сервером.
- default_encoding
- кодировка, если неизвестна (по умолчанию: "utf-8").
- dump_errors
- отображать ошибки в логе.
- environment
- текущее окружение, по умолчанию, значение ENV['APP_ENV'] или "development", если ENV['APP_ENV'] недоступна.
- logging
- использовать логер.
- lock
- создает блокировку для каждого запроса, которая гарантирует обработку только одного запроса в текущий момент времени в Ruby процессе.
- Включайте, если ваше приложение не потоко-безопасно (thread-safe). Отключено по умолчанию.
- method_override
- использовать "магический" параметр _method, для поддержки PUT/DELETE форм в браузерах, которые не поддерживают эти методы.
- port
- порт, на котором будет работать сервер. Используется только встроенным сервером.
- prefixed_redirects
- добавлять или нет параметр request.script_name к редиректам, если не задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя как redirect to('/foo'). Отключено по умолчанию.
- protection
- включена или нет защита от атак. Смотрите секцию выше.
- public_dir
- Алиас для public_folder.
- public_folder
- путь к директории, откуда будут раздаваться статические файлы. Используется, только если включена раздача статических файлов (см. опцию static ниже).
- reload_templates
- перезагружать или нет шаблоны на каждый запрос. Включено в режиме разработки.
- root
- путь к корневой директории проекта.
- raise_errors
- выбрасывать исключения (будет останавливать приложение). По умолчанию включено только в окружении test.
- run
- если включено, Sinatra будет самостоятельно запускать веб-сервер. Не включайте, если используете rackup или аналогичные средства.
- running
- работает ли сейчас встроенный сервер? Не меняйте эту опцию!
- server
- сервер или список серверов, которые следует использовать в качестве встроенного сервера. По умолчанию: ['thin', 'mongrel', 'webrick'], порядок задает приоритет.
- sessions
- включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. Смотрите секцию "Использование сессий" выше.
- show_exceptions
- показывать исключения/стек вызовов (stack trace) в браузере. По умолчанию включено только в окружении development.
- Может быть установлено в :after_handler для запуска специфичной для приложения обработки ошибок, перед показом трассировки стека в браузере.
- static
- должна ли Sinatra осуществлять раздачу статических файлов.
- Отключите, когда используете какой-либо веб-сервер для этой цели.
- Отключение значительно улучшит производительность приложения.
- По умолчанию включено в классических и отключено в модульных приложениях.
- static_cache_control
- когда Sinatra отдает статические файлы, используйте эту опцию, чтобы добавить им заголовок Cache-Control. Для этого используется метод-помощник cache_control. По умолчанию отключено.
- Используйте массив, когда надо задать несколько значений: set :static_cache_control, [:public, :max_age => 300]
- threaded
- если включено, то Thin будет использовать EventMachine.defer для обработки запросов.
- traps
- должна ли Синатра обрабатывать системные сигналы или нет.
- views
- путь к директории с шаблонами.
Есть 3 предопределенных режима, окружения: "development"
, "production"
и
"test"
. Режим может быть задан через переменную окружения APP_ENV
.
Значение по умолчанию — "development"
. В этом режиме работы все шаблоны
перезагружаются между запросами. А также задаются специальные обработчики
not_found
и error
, чтобы вы могли увидеть стек вызовов. В окружениях
"production"
и "test"
шаблоны по умолчанию кэшируются.
Для запуска приложения в определенном окружении используйте ключ -e
ruby my_app.rb -e [ENVIRONMENT]
Вы можете использовать предопределенные методы development?
, test?
и
+production?, чтобы определить текущее окружение.
Обработчики ошибок исполняются в том же контексте, что и маршруты, и
before
-фильтры, а это означает, что всякие прелести вроде haml
, erb
,
halt
и т.д. доступны и им.
Когда выброшено исключение Sinatra::NotFound
, или кодом ответа является 404,
то будет вызван not_found
обработчик:
not_found do
'This is nowhere to be found.'
end
Обработчик ошибок error
будет вызван, когда исключение выброшено из блока
маршрута, либо из фильтра. Объект-исключение доступен как переменная
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
Также вы можете установить обработчик ошибок для кода состояния HTTP:
error 403 do
'Access forbidden'
end
get '/secret' do
403
end
Либо набора кодов:
error 400..510 do
'Boom'
end
Sinatra устанавливает специальные not_found
и error
обработчики, когда
приложение запущено в режиме разработки (окружение :development
).
Sinatra использует Rack, минимальный стандартный интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — компонентов, находящихся "между" сервером и вашим приложением, которые отслеживают и/или манипулируют HTTP запросами/ответами для предоставления различной функциональности.
В Sinatra очень просто использовать такие "прослойки" с помощью метода use
:
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
) их вручную.
Вы можете найти полезные прослойки в rack, rack-contrib, или в Rack 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
Описание своего приложения самым простейшим способом (с помощью DSL верхнего
уровня, классический стиль) отлично работает для крохотных приложений. В таких
случаях используется конфигурация, рассчитанная на микро-приложения
(единственный файл приложения, ./public
и ./views
директории, логирование,
страница информации об исключении и т.д.). Тем не менее, такой метод имеет
множество недостатков при создании компонентов, таких как Rack middleware
("прослоек"), Rails metal, простых библиотек с серверными компонентами,
расширений Sinatra. И тут на помощь приходит 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, будут импортированы в глобальное пространство имен. - Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс
Sinatra::Base
.
Sinatra::Base
— это чистый лист. Большинство опций, включая встроенный
сервер, по умолчанию отключены. Смотрите
Опции и конфигурация
для детальной информации об опциях и их поведении.
Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего плохого. Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное приложение.
Основным недостатком классического стиля является тот факт, что у вас может быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете использовать больше, переключайтесь на модульный стиль. Вы можете смело смешивать модульный и классический стили.
Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках:
Опция Классический Модульный
app_file файл с приложением файл с подклассом Sinatra::Base
run $0 == app_file false
logging true false
method_override true false
inline_templates true false
static true File.exist?(public_folder)
Есть два общепринятых способа запускать модульные приложения: запуск напрямую
с помощью run!
:
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
# ... здесь код приложения ...
# запускаем сервер, если исполняется текущий файл
run! if app_file == $0
end
Затем:
ruby my_app.rb
Или с помощью конфигурационного файла config.ru
, который позволяет
использовать любой Rack-совместимый сервер приложений.
# config.ru
require './my_app'
run MyApp
Запускаем:
rackup -p 4567
Файл приложения:
# app.rb
require 'sinatra'
get '/' do
'Hello world!'
end
И соответствующий config.ru
:
require './app'
run Sinatra::Application
Вот несколько причин, по которым вы, возможно, захотите использовать
config.ru
:
- вы хотите разворачивать свое приложение на различных Rack-совместимых серверах (Passenger, Unicorn, Heroku, ...);
- вы хотите использовать более одного подкласса
Sinatra::Base
; - вы хотите использовать Sinatra только в качестве "прослойки" Rack.
Совсем необязательно переходить на использование config.ru
лишь потому,
что вы стали использовать модульный стиль приложения. И необязательно
использовать модульный стиль, чтобы запускать приложение с помощью
config.ru
.
Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra приложение само может быть добавлено к любому Rack endpoint в качестве "прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra приложение, или приложение, основанное на Rack (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
# "прослойка" будет запущена перед фильтрами
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 приложения "на лету" (например, из другого
приложения). Это возможно с помощью Sinatra.new
:
require 'sinatra/base'
my_app = Sinatra.new { get('/') { "hi" } }
my_app.run!
Этот метод может принимать аргументом приложение, от которого следует наследоваться:
# 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
Это особенно полезно для тестирования расширений Sinatra и при использовании Sinatra внутри вашей библиотеки.
Благодаря этому, использовать Sinatra как "прослойку" очень просто:
require 'sinatra/base'
use Sinatra do
get('/') { ... }
end
run RailsProject::Application
Текущая область видимости определяет методы и переменные, доступные в данный момент.
Любое Sinatra приложение соответствует подклассу Sinatra::Base
. Если вы
используете DSL верхнего уровня (require 'sinatra'
), то этим классом будет
Sinatra::Application
, иначе это будет подкласс, который вы создали вручную.
На уровне класса вам будут доступны такие методы, как get
или before
, но
вы не сможете получить доступ к объектам request
или session
, так как
существует только один класс приложения для всех запросов.
Опции, созданные с помощью set
, являются методами уровня класса:
class MyApp < Sinatra::Base
# Я в области видимости приложения!
set :foo, 42
foo # => 42
get '/foo' do
# Я больше не в области видимости приложения!
end
end
У вас будет область видимости приложения внутри:
- тела вашего класса приложения;
- методов, определенных расширениями;
- блока, переданного в
helpers
; - блоков, использованных как значения для
set
; - блока, переданного в
Sinatra.new
.
Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами:
- через объект, переданный блокам конфигурации (
configure { |c| ... }
); settings
внутри области видимости запроса.
Для каждого входящего запроса будет создан новый экземпляр вашего приложения,
и все блоки обработчика будут запущены в этом контексте. В этой области
видимости вам доступны request
и session
объекты, вызовы методов
рендеринга, такие как erb
или haml
. Вы можете получить доступ к области
видимости приложения из контекста запроса, используя метод-помощник
settings
:
class MyApp < Sinatra::Base
# Я в области видимости приложения!
get '/define_route/:name' do
# Область видимости запроса '/define_route/:name'
@value = 42
settings.get("/#{params['name']}") do
# Область видимости запроса "/#{params['name']}"
@value # => nil (другой запрос)
end
"Route defined!"
end
end
У вас будет область видимости запроса в:
- get/head/post/put/delete/options блоках;
- before/after фильтрах;
- методах-помощниках;
- шаблонах/отображениях.
Область видимости делегирования просто перенаправляет методы в область
видимости класса. Однако, она не полностью ведет себя как область видимости
класса, так как у вас нет привязки к классу. Только методы, явно помеченные
для делегирования, будут доступны, а переменных/состояний области видимости
класса не будет (иначе говоря, у вас будет другой self
объект). Вы можете
непосредственно добавить методы делегирования, используя
Sinatra::Delegator.delegate :method_name
.
У вас будет контекст делегирования внутри:
- привязки верхнего уровня, если вы сделали
require 'sinatra'
; - объекта, расширенного с помощью
Sinatra::Delegator
.
Посмотрите сами в код: вот примесь Sinatra::Delegator расширяет главный объект.
Sinatra приложения могут быть запущены напрямую:
ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
Опции включают:
-h # раздел помощи
-p # указание порта (по умолчанию 4567)
-o # указание хоста (по умолчанию 0.0.0.0)
-e # указание окружения, режима (по умолчанию development)
-s # указание rack сервера/обработчика (по умолчанию thin)
-x # включить мьютекс-блокировку (по умолчанию выключена)
Данный раздел является перефразированным [ответом пользователя Konstantin][so-answer] на StackOverflow
Sinatra не навязывает каких-либо моделей параллелизма, но для этих целей можно использовать любой Rack обработчик, например Thin, Puma или WEBrick. Сама по себе Sinatra потокобезопасна, поэтому нет никаких проблем в использовании поточной модели параллелизма в Rack обработчике. Это означает, что когда запускается сервер, вы должны указать правильный метод вызова для конкретного Rack обработчика. Пример ниже показывает, как можно запустить мультитредовый Thin сервер:
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
Чтобы запустить сервер, вы должны выполнить следующую команду:
thin --threaded start
[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999)
Следующие версии Ruby официально поддерживаются:
- Ruby 1.8.7
- 1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или Rubinius. Поддержка 1.8.7 не будет прекращена до выхода Sinatra 2.0 и Ruby 2.0, разве что в случае релиза 1.8.8 (что маловероятно). Но даже тогда, возможно, поддержка не будет прекращена. Ruby 1.8.6 больше не поддерживается. Если вы хотите использовать 1.8.6, откатитесь до Sinatra 1.2, которая будет получать все исправления ошибок до тех пор, пока не будет выпущена Sinatra 1.4.0.
- Ruby 1.9.2
- 1.9.2 полностью поддерживается и рекомендована к использованию. Не используйте 1.9.2p0, известно, что эта версия очень нестабильна при использовании Sinatra. Эта версия будет поддерживаться по крайней мере до выхода Ruby 1.9.4/2.0, а поддержка последней версии 1.9 будет осуществляться до тех пор, пока она поддерживается командой разработчиков Ruby.
- Ruby 1.9.3
- 1.9.3 полностью поддерживается. Заметьте, что переход на 1.9.3 с ранних версий сделает недействительными все сессии.
- Rubinius
- Rubinius официально поддерживается (Rubinius >= 1.2.4), всё, включая все языки шаблонов, работает. Предстоящий релиз 2.0 также поддерживается.
- JRuby
- JRuby официально поддерживается (JRuby >= 1.6.5). Нет никаких проблем с использованием альтернативных шаблонов. Тем не менее, если вы выбираете JRuby, то, пожалуйста, посмотрите на JRuby Rack-серверы, так как Thin не поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все еще экспериментальная, что на данный момент затрагивает только RDiscount, Redcarpet и RedCloth.
Мы также следим за предстоящими к выходу версиями Ruby.
Следующие реализации Ruby не поддерживаются официально, но известно, что на них запускается Sinatra:
- старые версии JRuby и Rubinius;
- Ruby Enterprise Edition;
- MacRuby, Maglev, IronRuby;
- Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию).
То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает — это не наша проблема, а их.
Мы также запускаем наши CI-тесты на версии Ruby, находящейся в разработке (предстоящей 2.0.0), и на 1.9.4, но мы не можем ничего гарантировать, так как они находятся в разработке. Предполагается, что 1.9.4p0 и 2.0.0p0 будут поддерживаться.
Sinatra должна работать на любой операционной системе, в которой есть одна из указанных выше версий Ruby.
Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой версии Ruby до 1.8.7.
Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать свое приложение вместе с кодом из master ветки Sinatra, она весьма стабильна.
Мы также время от времени выпускаем предварительные версии, так что вы можете делать так:
gem install sinatra --pre
Чтобы воспользоваться некоторыми самыми последними возможностями.
Если вы хотите запускать свое приложение с последней версией Sinatra, то рекомендуем использовать Bundler.
Сначала установите Bundler, если у вас его еще нет:
gem install bundler
Затем создайте файл Gemfile
в директории вашего проекта:
source :rubygems
gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
# другие зависимости
gem 'haml' # например, если используете haml
gem 'activerecord', '~> 3.0' # может быть, вам нужен и ActiveRecord 3.x
Обратите внимание, вам нужно будет указывать все зависимости вашего приложения в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler автоматически скачает и добавит.
Теперь вы можете запускать свое приложение так:
bundle exec ruby myapp.rb
Sinatra использует Semantic Versioning, SemVer и SemVerTag.
- Веб-сайт проекта — Дополнительная документация, новости и ссылки на другие ресурсы.
- Участие в проекте — Обнаружили баг? Нужна помощь? Написали патч?
- Отслеживание проблем/ошибок
- Группы рассылки
- IRC: #sinatra на http://freenode.net
- Sinatra и Друзья на Slack, а так же ссылка для инвайта.
- Sinatra Book учебник и сборник рецептов
- Sinatra Recipes сборник рецептов
- API документация к последнему релизу или текущему HEAD на http://www.rubydoc.info/
- Сервер непрерывной интеграции