Внимание: Этот документ является переводом английской версии и может быть устаревшим
Sinatra — это предметно-ориентированный каркас (DSL) для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий:
# myapp.rb require 'sinatra' get '/' do 'Hello world!' end
Установите gem:
gem install sinatra
и запустите приложение при помощи:
ruby myapp.rb
Оцените результат: http://localhost:4567
Имейте ввиду, что изменения в коде не будут видны до тех пор, пока вы не перезапустите сервер. Пожалуйста, перезагружайте сервер каждый раз как вносите изменения или добавьте в проект sinatra/reloader.
Рекомендуется также установить Thin сервер (gem install thin
), который автоматически работает с Sinatra приложениями.
yield
и вложенные лэйаутыВ 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
Маршруты сверяются с запросом в порядке очерёдности их записи в файле приложения. Первый же совпавший с запросом маршрут и будет вызван.
Маршруты с конечным слэшем отличаются от маршрутов без него:
get '/foo' do # не соответствует "GET /foo/" 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| # соответствует "GET /hello/foo" и "GET /hello/bar", # где params['name'] - это 'foo' или 'bar' # n хранит params['name'] "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
Маршруты также могут использовать параметры запроса:
get '/posts' do # соответствует "GET /posts?title=foo&author=bar" title = params['title'] author = params['author'] # используются переменные title и author; запрос не обязателен для маршрута /posts end
Имеейте ввиду: если вы не отключите защиту от обратного пути в директориях (path traversal, см. ниже), путь запроса может быть изменён до начала поиска подходящего маршрута.
Вы можете настроить Mustermann опции, используемые для данного маршрута, путём передачи в :mustermann_opts
хэш:
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do # в точности соответствует /posts, с явной привязкой "If you match an anchored pattern clap your hands!" end
Это похоже на условие, но это не так! Эти опции будут объеденины в глобальный :mustermann_opts
хэш, описанный ниже.
Маршруты могут включать в себя различные условия совпадений, такие как, например, строка агента пользователя (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
provides
ищет заголовок запроса Accept
.
Вы можете с лёгкостью задавать собственные условия:
set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end
Ипользуйте splat-оператор (*
) для условий, которые принимают несколько аргументов:
set(:auth) do |*roles| # <- обратите внимание на звёздочку 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
, который передает только строковые типы данных в этот блок;Таким образом легко можно реализовать, например, потоковую передачу:
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
Или с использованием негативной опережающей проверки (отрицательное look-ahead условие):
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
.
Доступные опции:
По умолчанию в качестве пути для хранения шаблонов принята директория ./views
. Чтобы назначить другую директорию с шаблонами необходимо изменить настройки:
set :views, settings.root + '/templates'
Важное замечание: вы всегда должны ссылаться на шаблоны при помощи символов (Symbol), даже тогда, когда они расположены в поддиректории (в этом случае используйте конструкции вида :'subdir/template'
). Вы должны использовать символы в связи с тем, что в ином случае шаблонизаторы попросту отображают любые строки, переданные им.
get '/' do haml '%div.title Hello World' end
Отобразит шаблон, содержимое которого передано строкой. Опционально можно указать дополнительные опции :path
и :line
для того, чтобы улучшить бэктрейс. Делайте это в том случае, если строка определена в некотором файле, к которому можно указать путь и номер строки, где расположена исходная строка:
get '/' do haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 end
Некоторые языки шаблонов имеют несколько реализаций. Для того, чтобы указать конкретную реализацию, которую вы хотите использовать, вам следует просто подключить нужную библиотеку:
require 'rdiscount' # или 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
и 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
), то вы почти всегда будете передавать в шаблон локальные переменные. Лэйауты также могут быть описаны при помощи WLang.
Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра, установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах:
get '/:id' do @foo = Foo.find(params['id']) haml '%h1= @foo.name' end
Вы также можете установить их при помощи хэша локальных переменных:
get '/:id' do foo = Foo.find(params['id']) haml '%h1= bar.name', :locals => { :bar => foo } end
Это обычный подход, применяемый тогда, когда шаблоны рендерятся как части других шаблонов.
yield
и вложенные лэйаутыЛэйаут (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.
Для того, чтобы реализовать собственный механизм поиска шаблона, необходимо написать метод #find_template
:
configure do set :views [ './views/a', './views/b' ] end def find_template(views, name, engine, &block) Array(views).each do |v| super(v, name, engine, &block) end end
before
-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты, и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах:
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
Для того, чтобы повысить безопасность, данные сессии в файле 'cookie' подписываются ключом сессии с использованием HMAC-SHA1
. Этот ключ сессии должен быть оптимальным криптографическим 'secure random' значением соответствующей длины, которая для HMAC-SHA1
больше или равна 64 байтам (512 бит, 128 шестнадцатеричных символов). Не рекомендуется использовать ключ, длина которого менее 32 байт (256 бит, 64 шестнадцатеричных символа). Поэтому очень важно, чтобы вы не просто составили значение ключа, а использовали безопасный генератор случайных чисел для его создания. Люди очень плохо придумывают случайные значения.
По умолчанию, Sinatra создаёт для вас безопасный случайный ключ сессии из 32 байт, однако он будет меняться при каждом перезапуске приложения. Если у вас есть несколько экземпляров вашего приложения, и вы доверили Sinatra генерацию ключа, то каждый экземпляр будет иметь отличный ключ сессии, что, вероятно, не совсем то, что вам необходимо.
Для лучшей безопасности и удобства использования рекомендуется генерировать случайный безопасный ключ и хранить его в переменной среды на каждом хосте, на котором запущено приложение, чтобы все экземпляры вашего приложения использовали один и тот же ключ. Вы должны периодически менять значение ключа сессии на новое. Вот несколько примеров того, как вы можете создать 64-байтный ключ и установить его:
Генерация ключа сессии
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)" 99ae8af...snip...ec0f262ac
Генерация ключа сессии (бонусные пункты)
Используйте гем 'sysrandom'. Предпочтительнее использовать системные средства RNG для генерации случайных значений вместо пространства пользователя OpenSSL
, который в настоящее время по умолчанию используется в MRI Ruby:
$ gem install sysrandom Создание собственных расширений. Это может занять некоторое время... Успешно установлен sysrandom-1.x 1 gem установлен $ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)" 99ae8af...snip...ec0f262ac
Переменная среды для ключа сессии
Задайте переменной среды SESSION_SECRET
значение, которое вы сгенерировали. Данная переменная автоматически будет использована Sinatra. Сделайте это значение постоянным при перезагрузке вашего сервера. Поскольку метод для генерации будет различным в разных системах, то код ниже приведён только в качестве примера:
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
Конфигурация приложения
В целях безопасности настройте конфигурацию вашего приложения таким образом, чтобы оно генерировало случайный безопасный ключ тогда, когда переменная среды SESSION_SECRET
не доступна.
В качестве бонусных пунктов здесь тоже используйте гем 'sysrandom'gem:
require 'securerandom' # -или- require 'sysrandom/securerandom' set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
Если вы хотите больше настроек для сессий, вы можете задать их, передав хэш опций в параметр sessions
:
set :sessions, :domain => 'foo.com'
Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах foo.com, добавьте . перед доменом:
set :sessions, :domain => '.foo.com'
Обратите внимание на то, что при использовании enable :sessions
все данные сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например, сохранение больших объёмов данных увеличит ваш трафик). В таком случае вы можете использовать альтернативную Rack "прослойку" (middleware), реализующую механизм сессий. Для этого используете один из способов ниже:
enable :sessions set :session_store, Rack::Session::Pool
Или установите параметры сессии при помощи хэша опций:
set :sessions, :expire_after => 2592000 set :session_store, Rack::Session::Pool
Вы также можете не вызывать enable :sessions
, а вместо этого использовать необходимую вам Rack прослойку так же, как вы это обычно делаете.
Очень важно обратить внимание на то, что когда вы используете этот метод, основной способ защиты сессии не будет включён по умолчанию.
Вам также потребуется добавить следующие Rack middleware для этого:
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; https://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
Также клиент может закрыть соединение при попытке записи в сокет. В связи с этим рекомендуется выполнить проверку out.closed?
прежде, чем пытаться произвести запись.
В области видимости запроса метод 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
Возможные опции:
Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) при помощи 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, 2016') "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]
По умолчанию Sinatra будет устанавливать session based
защиту только если включена опция :sessions
. См. "Использование сессий"". Иногда вы захотите настроить сессии "вне" приложения Sinatra, например, в config.ru или при помощи отдельного экземпляра Rack::Builder
. В таком случае вы также можете настроить session based
защиту, передав опцию :session
:
set :protection, :session => true
environment
настроена на "development"). Используется только встроенным сервером.
Есть 3 предопределённых режима работы приложения (окружения): "development"
, "production"
и "test"
. Режим может быть задан через переменную окружения APP_ENV
. Значение по умолчанию — "development"
. В этом режиме работы все шаблоны перезагружаются между запросами, а также задаются специальные обработчики not_found
и error
, чтобы вы могли увидеть стек вызовов. В окружениях "production"
и "test"
шаблоны по умолчанию кэшируются.
Для запуска приложения в определённом окружении установите переменную окружения APP_ENV
:
APP_ENV=production ruby my_app.rb
Вы можете использовать предопределённые методы development?
, test?
и production?
, чтобы определить текущее окружение.
get '/' do if settings.development? "development!" else "not development!" end end
Обработчики ошибок исполняются в том же контексте, что и маршруты и before
-фильтры, а это означает, что всякие прелести вроде haml
, erb
, halt
и т.д. доступны и им.
В случае возникновения исключения Sinatra::NotFound
или возврата кода ответа 404 будет вызван обработчик not_found
:
not_found do 'This is nowhere to be found.' end
Обработчик ошибок error
будет вызван тогда, когда исключение выброшено из блока маршрута либо из фильтра. Тем не менее, обратите внимание на то, что в режиме разработки он будет запускаться только в том случае, если вы установите опцию "show exceptions" на : after_handler
:
set :show_exceptions, :after_handler
Объект-исключение доступен как переменная 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 позволяет очень просто создавать пайплайны из подобных Rack "прослоек" при помощи метода 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. Разработчики гема Sinatra рекомендуют использовать Rack::Test:
require 'my_sinatra_app' require 'minitest/autorun' require 'rack/test' class MyAppTest < Minitest::Test include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_user_agent get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end
Примечание: если вы используете Sinatra в модульном стиле, замените Sinatra::Application
в примере выше на имя класса вашего приложения.
Описание вашего приложения на верхнем уровне хорошо работает для микроприложений, но имеет значительные недостатки при создании многоразовых компонентов, таких как Rack "прослойка", Rails metal, простые библиотеки с серверным компонентом или даже расширения Sinatra. Верхний уровень предполагает конфигурацию стиля микроприложений (например, одиночный файл приложения, ./public
и ./views
, каталоги, логирование, страницу подробных сведений об исключениях и т.д.). И тут на помощь приходит Sinatra::Base
:
require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end
Методы, доступные подклассам Sinatra::Base
идентичны тем, что доступны приложениям при помощи DSL верхнего уровня. Большинство таких приложений могут быть конвертированы в Sinatra::Base
компоненты при помощи двух модификаций:
sinatra/base
вместо sinatra
, иначе все DSL методы, предоставляемые Sinatra, будут импортированы в глобальное пространство имён.Sinatra::Base
.Sinatra::Base
— это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены. Смотрите Опции и конфигурация для детальной информации об опциях и их поведении. Если вы хотите, чтобы поведение было более похоже на то, когда вы определяете своё приложение на верхнем уровне (также известно как классический стиль), вы можете унаследоваться от Sinatra::Application
:
require 'sinatra/base' class MyApp < Sinatra::Application get '/' do 'Hello world!' end end
Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего плохого. Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное приложение.
Основным недостатком классического стиля является тот факт, что у вас может быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете использовать больше, переключайтесь на модульный стиль. Вы можете смело смешивать модульный и классический стили.
Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках:
Опция | Классический | Модульный | Модульный |
---|---|---|---|
app_file | файл с приложением | файл с подклассом Sinatra::Base | файл с подклассом Sinatra::Application |
run | $0 == app_file | false | false |
logging | true | false | true |
method_override | true | false | true |
inline_templates | true | false | true |
static | true | File.exist?(public_folder) | true |
Есть два общепринятых способа запускать модульные приложения: запуск напрямую при помощи run!
:
# my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... здесь код приложения ... # запускаем сервер, если исполняется текущий файл run! if app_file == $0 end
Затем:
ruby my_app.rb
Или при помощи конфигурационного файла config.ru
, который позволяет использовать любой Rack-совместимый сервер приложений:
# config.ru (запускается при помощи Rackup) 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
рекомендовано если:
Sinatra::Base
;Совсем необязательно переходить на использование config.ru
лишь потому, что вы стали использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы запускать приложение при помощи config.ru
.
Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra приложение само может быть добавлено к любому Rack endpoint в качестве "прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra приложение или любое другое приложение, основанное на Rack (Rails/Hamami/Roda/...):
require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params['name'] == 'admin' && params['password'] == 'admin' session['user_name'] = params['name'] else redirect '/login' end end end class MyApp < Sinatra::Base # "прослойка" будет запущена перед фильтрами 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 (запускается при помощи Rackup) require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end
Это особенно полезно для тестирования расширений Sinatra и при использовании Sinatra внутри вашей библиотеки.
Это также значительно упрощает использование Sinatra в качестве прослойки:
require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application
Текущая область видимости определяет методы и переменные, доступные в данный момент.
Любое Sinatra приложение соответствует подклассу Sinatra::Base
. Если вы используете DSL верхнего уровня (require 'sinatra'
), то этим классом будет Sinatra::Application
, иначе это будет подкласс, который вы создали вручную. На уровне класса вам будут доступны такие методы, как 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
У вас будет привязка к области видимости запроса в:
Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, она не полностью ведет себя как область видимости класса, так как у вас нет привязки к классу. Только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой self
объект). Вы можете непосредственно добавить методы делегирования, используя Sinatra::Delegator.delegate :method_name
.
У вас будет контекст делегирования внутри:
require "sinatra"
;Sinatra::Delegator
.Посмотрите сами в код: вот примесь Sinatra::Delegator расширяет главный объект.
Sinatra приложения могут быть запущены напрямую:
ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
Поддерживаемые опции:
-h # раздел помощи -p # указание порта (по умолчанию 4567) -o # указание хоста (по умолчанию 0.0.0.0) -e # указание окружения, режима (по умолчанию development) -s # указание rack сервера/обработчика (по умолчанию thin) -q # включить тихий режим для сервера (по умолчанию выключен) -x # включить мьютекс-блокировку (по умолчанию выключена)
Данный раздел является перефразированным ответом пользователя Konstantin на 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
Следующие версии Ruby официально поддерживаются:
Версии Ruby ниже 2.2.2 более не поддерживаются в Sinatra 2.0.
Мы также следим за предстоящими к выходу версиями Ruby.
Следующие реализации Ruby не поддерживаются официально, однако известно, что на них запускается Sinatra:
То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает — это не наша проблема, а их.
Мы также запускаем наши CI-тесты на ruby-head (будущие версии MRI), но мы не можем ничего гарантировать, так как ведётся постоянная разработка. Предполагается, что предстоящие релизы 2.x будут полностью поддерживаться.
Sinatra должна работать на любой операционной системе, в которой есть одна из указанных выше версий Ruby.
Если вы запускаете MacRuby, вы должны выполнить gem install control_tower
.
Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой версии Ruby ниже 2.2.
Если вы хотите использовать самый последний релиз Sinatra, не бойтесь запускать своё приложение вместе с кодом из master ветки Sinatra, так как она весьма стабильна.
Мы также время от времени выпускаем предварительные версии, которые вы можете установить следующим образом:
gem install sinatra --pre
таким образом вы сможете воспользоваться некоторыми самыми последними возможностями.
Если вы хотите запускать своё приложение с последней версией Sinatra, то рекомендуем использовать Bundler.
Для начала установите Bundler, если у вас его ещё нет:
gem install bundler
Затем создайте файл Gemfile
в директории вашего проекта:
source 'https://rubygems.org' gem 'sinatra', :github => 'sinatra/sinatra' # другие зависимости gem 'haml' # в том случае, если используете haml
Имейте ввиду, что вам необходимо будет указывать все зависимости вашего приложения в Gemfile
. Необходимые зависимости самой библиотеки Sinatra (Rack и Tilt) будут автоматически скачаны и добавлены Bundler.
Теперь вы можете запускать своё приложение следующим образом:
bundle exec ruby myapp.rb
Sinatra использует Semantic Versioning: как SemVer, так и SemVerTag.