Skip to content

Latest commit

 

History

History
3190 lines (2504 loc) · 117 KB

README.ru.md

File metadata and controls

3190 lines (2504 loc) · 117 KB

Sinatra

Build Status

Внимание: Этот документ является переводом английской версии и может быть устаревшим

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

Маршруты сверяются с запросом в порядке очередности их записи в файле приложения. Первый же совпавший с запросом маршрут и будет вызван.

Маршруты с конечным слэшем отличаются от маршрутов без него:

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

Для условия, которое принимает несколько параметров, используйте звездочку:

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
layout_options
Специальные опции, используемые только для рендеринга лэйаута. Например: set :rdoc, :layout_options => { :views => 'views/layouts' }

По умолчанию считается, что шаблоны находятся в директории ./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
Пример haml :index, :format => :html5

Erb шаблоны

Зависимости erubis или erb (включен в Ruby)
Расширения файлов .erb, .rhtml or .erubis (только Erubis)
Пример erb :index

Builder шаблоны

Зависимости builder
Расширения файлов .builder
Пример builder { |xml| xml.em "hi" }

Блок также используется и для встроенных шаблонов (см. пример).

Nokogiri шаблоны

Зависимости nokogiri
Расширения файлов .nokogiri
Пример nokogiri { |xml| xml.em "hi" }

Блок также используется и для встроенных шаблонов (см. пример).

Sass шаблоны

Зависимости sass
Расширения файлов .sass
Пример sass :stylesheet, :style => :expanded

SCSS шаблоны

Зависимости sass
Расширения файлов .scss
Пример scss :stylesheet, :style => :expanded

Less шаблоны

Зависимости less
Расширения файлов .less
Пример less :stylesheet

Liquid шаблоны

Зависимости liquid
Расширения файлов .liquid
Пример liquid :index, :locals => { :key => 'value' }

Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме yield), то вы почти всегда будете передавать в шаблон локальные переменные.

Markdown шаблоны

Зависимости Любая из библиотек: 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.

Textile шаблоны

Зависимости 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
Пример 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.

AsciiDoc шаблоны

Зависимости Asciidoctor
Расширения файлов .asciidoc, .adoc и .ad
Пример asciidoc :README, :layout_engine => :erb

Так как в AsciiDoc шаблонах невозможно вызывать методы из Ruby напрямую, то вы почти всегда будете передавать в шаблон локальные переменные.

Radius шаблоны

Зависимости Radius
Расширения файлов .radius
Пример radius :index, :locals => { :key => 'value' }

Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы почти всегда будете передавать в шаблон локальные переменные.

Markaby шаблоны

Зависимости Markaby
Расширения файлов .mab
Пример markaby { h1 "Welcome!" }

Блок также используется и для встроенных шаблонов (см. пример).

RABL шаблоны

Зависимости Rabl
Расширения файлов .rabl
Пример rabl :index

Slim шаблоны

Зависимости Slim Lang
Расширения файлов .slim
Пример slim :index

Creole шаблоны

Зависимости 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.

MediaWiki шаблоны

Зависимости 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 шаблоны

Зависимости CoffeeScript и способ запускать JavaScript
Расширения файлов .coffee
Пример coffee :index

Stylus шаблоны

Зависимости Stylus и способ запускать JavaScript
Расширение файла .styl
Пример stylus :index

Перед тем, как использовать шаблоны стилус, загрузите stylus и stylus/tilt:

require 'sinatra'
require 'stylus'
require 'stylus/tilt'

get '/' do
  stylus :example
end

Yajl шаблоны

Зависимости yajl-ruby
Расширения файлов .yajl
Пример yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'

Содержимое шаблона интерпретируется как код на Ruby, а результирующая переменная json затем конвертируется с помощью #to_json.

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

Опции :callback и :variable используются для "декорирования" итогового объекта.

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

WLang шаблоны

Зависимости 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

Это обычный подход, когда шаблоны рендерятся как части других шаблонов.

Шаблоны с yield и вложенные раскладки (layout)

Раскладка (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

Использование пользовательской логики для поиска шаблона

Для того, чтобы реализовать свой собственный механизм поиска шаблона, необходимо написать свой собственный #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 шестнадцатеричных символа). Поэтому очень важно, чтобы вы не просто составили значение ключа, а использовали генератор 'secure random' чисел для его создания. Люди очень плохо генерируют случайные значения.

По умолчанию, Sinatra создаёт для вас 'secure random' ключ сессии из 32 байт, но он будет меняться при каждом перезапуске приложения. Если у вас есть несколько экземпляров вашего приложения, и вы позволяете чтобы Sinatra генерировала ключ, то каждый экземпляр будет иметь отличный ключ сессии, который, вероятно, не тот, который вы хотите.

Для лучшей безопасности и удобства использования рекомендуется генерировать 'secure random' ключ и хранить его в переменной среды на каждом хосте, на котором запущено приложение, чтобы все экземпляры вашего приложения использовали один и тот же ключ. Вы должны периодически менять значение ключа сессии. Вот несколько примеров того, как вы можете создать ключ на 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 SESSION_SECRET значение, которое вы сгенерировали. Сделайте это значение постоянным при перезагрузке вашего хоста. Поскольку метод для генерации будет различным в разных системах, то код ниже приведён только в качестве примера:

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

Конфигурация приложения

Настройте конфигурацию вашего приложения в безопасном режиме для 'secure random' ключа если переменная среды 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, а вместо этого вызывать необходимую вам прослойку так же, как вы это обычно делаете.

Очень важно обратить внимание на то, что когда вы используете этот метод, основной способ защиты сессии не будет включен по умолчанию.

Если вы хотите включить защиту, вам нужно добавить следующие строчки:

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

Также клиент может закрыть соединение при попытке записи в сокет. В связи с этим рекомендуется проверить 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'].

Mime-типы

Когда вы используете 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, вам следует использовать метод 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. Если вы используете этот вспомогательный метод, 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, 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

Доступные настройки

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 или localhost если ваша `environment` настроена на разработку). Используется только встроенным сервером.
default_encoding
кодировка, если неизвестна (по умолчанию: "utf-8").
dump_errors
отображать ошибки в логе.
environment
текущее окружение, по умолчанию, значение ENV['APP_ENV'] или "development", если ENV['APP_ENV'] недоступна.
logging
использовать логер.
lock
создает блокировку для каждого запроса, которая гарантирует обработку только одного запроса в текущий момент времени в Ruby процессе.
включайте, если ваше приложение не потоко-безопасно (thread-safe). Отключено по умолчанию.
method_override
использовать "магический" параметр _method, для поддержки PUT/DELETE форм в браузерах, которые не поддерживают эти методы.
mustermann_opts
хэш настроек по умолчанию для перехода к Mustermann.new при компиляции маршрутов маршрутизации.
port
порт, на котором будет работать сервер. Используется только встроенным сервером.
prefixed_redirects
добавлять или нет параметр request.script_name к редиректам, если не задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя как redirect to('/foo'). Отключено по умолчанию.
protection
включена или нет защита от атак. Смотрите секцию защиты от атак выше.
public_dir
алиас для public_folder.
public_folder
путь к общедоступной директории, откуда будут раздаваться файлы. Используется, только если включена раздача статических файлов (см. опцию static ниже). Берётся из настройки app_file, если не установлена.
quiet
отключает журналы, созданные командами запуска и остановки Sinatra. false по умолчанию.
reload_templates
перезагружать или нет шаблоны на каждый запрос. Включено в режиме разработки.
root
путь к корневой директории проекта. Берётся из настройки app_file, если не установлен.
raise_errors
выбрасывать исключения (будет останавливать приложение). По умолчанию включено только в окружении "test".
run
если включено, Sinatra будет самостоятельно запускать веб-сервер. Не включайте, если используете rackup или аналогичные средства.
running
работает ли сейчас встроенный сервер? Не меняйте эту опцию!
server
сервер или список серверов, которые следует использовать в качестве встроенного сервера. Порядок задает приоритет, по умолчанию зависит от реализации Ruby.
sessions
включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. Смотрите секцию "Использование сессий" выше.
session_store
используется Rack "прослойка" сессии. По умолчанию 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
должна ли Sinatra обрабатывать системные сигналы или нет.
views
путь к директории с шаблонами. Берётся из настройки app_file, если не установлен.
x_cascade
Установлен ли заголовок X-Cascade если никакие маршруты не совпадают. true по умолчанию.

Режим, окружение

Есть 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 и т.д. доступны и им.

Not Found

Когда выброшено исключение Sinatra::NotFound, или кодом ответа является 404, то будет вызван not_found обработчик:

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

Error

Обработчик ошибок 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) чтобы отображать хорошие трассировки стека и дополнительную информацию об отладке в вашем браузере.

Rack "прослойка"

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

Примечание: если вы используете Sinatra в модульном стиле, замените Sinatra::Application в примере выше выше на имя класса вашего приложения.

Sinatra::Base — "прослойки", библиотеки и модульные приложения

Описание вашего приложения на верхнем уровне хорошо работает для микроприложений, но имеет значительные недостатки при создании многоразовых компонентов, таких как Rack "прослойка", Rails metal, простые библиотеки с серверным компонентом или даже расширения Sinatra. Верхний уровень предполагает конфигурацию стиля микроприложений (например, одиночный файл приложения, ./public и ./views, каталоги, логгирование, страницу подробных сведений об исключениях и т.д.). И тут на помощь приходит Sinatra::Base:

require 'sinatra/base'

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

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

Методы, доступные Sinatra::Base подклассам идентичны тем, что доступны приложениям в DSL верхнего уровня. Большинство таких приложений могут быть конвертированы в Sinatra::Base компоненты с помощью двух модификаций:

  • Вы должны подключать sinatra/base вместо sinatra, иначе все 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

Запуск классических приложений с config.ru

Файл приложения:

# app.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

И соответствующий config.ru:

require './app'
run Sinatra::Application

Когда использовать config.ru?

Вот несколько причин, по которым вы, возможно, захотите использовать config.ru:

  • вы хотите разворачивать свое приложение на различных Rack-совместимых серверах (Passenger, Unicorn, Heroku, ...);
  • вы хотите использовать более одного подкласса Sinatra::Base;
  • вы хотите использовать Sinatra только в качестве "прослойки" Rack.

Совсем необязательно переходить на использование config.ru лишь потому, что вы стали использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы запускать приложение с помощью config.ru.

Использование Sinatra в качестве "прослойки"

Не только сама 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

У вас будет область видимости запроса в:

  • get, head, post, put, delete, options, patch, link и unlink блоках;
  • before и after фильтрах;
  • методах-помощниках;
  • шаблонах/отображениях.

Область видимости делегирования

Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, она не полностью ведет себя как область видимости класса, так как у вас нет привязки к классу. Только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой 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 # включить мьютекс-блокировку (по умолчанию выключена)

Multi-threading

Данный раздел является перефразированным [ответом пользователя 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 2.2
2.2 полностью поддерживается и рекомендуется. В настоящее время нет планов отказаться от официальной поддержки.
Rubinius
Rubinius официально поддерживается (Rubinius >= 2.x). Рекомендуется выполнить gem install puma.
JRuby
Официально поддерживается последний стабильный релиз JRuby. Не рекомендуется использовать расширений на C в JRuby. Рекомендуется выполнить gem install trinidad.

Версии Ruby старше 2.2.2 больше не поддерживаются в Sinatra 2.0.

Мы также следим за предстоящими к выходу версиями Ruby.

Следующие реализации Ruby не поддерживаются официально, но известно, что на них запускается Sinatra:

  • старые версии JRuby и Rubinius;
  • Ruby Enterprise Edition;
  • MacRuby, Maglev, IronRuby;
  • Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию).

То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает — это не наша проблема, а их.

Мы также запускаем наши 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

Чтобы воспользоваться некоторыми самыми последними возможностями.

С помощью Bundler

Если вы хотите запускать свое приложение с последней версией 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.

Дальнейшее чтение