= Sinatra Внимание: Этот документ является переводом Английской версии и может быть устаревшим Sinatra — это предметно-ориентированный язык (DSL) для быстрого создания приложений на Ruby с приложением минимума усилий: # myapp.rb require 'sinatra' get '/' do 'Hello world!' end Установите gem и запустите приложение с помощью: gem install sinatra ruby -rubygems myapp.rb Результат будет тут: http://localhost:4567 Рекомендуется также запустить gem install thin, чтобы Sinatra могла использовать thin в качестве сервера. == Маршруты В Sinatra маршрут — это пара: HTTP метод и шаблон (образец) URL. Каждый маршрут ассоциирован с блоком: get '/' do .. что-то показать .. end post '/' do .. что-то создать .. end put '/' do .. что-то заменить .. end patch '/' do .. что-то изменить .. end delete '/' do .. что-то удалить .. end options '/' do .. что-то ответить .. end Маршруты сверяются с запросом по очередности определения. Первый же совпавший с запросом маршрут и будет вызван. Шаблоны маршрутов могут включать в себя параметры доступные в params xэше: get '/hello/:name' do # соответствует "GET /hello/foo" и "GET /hello/bar", # где params[:name] 'foo' или 'bar' "Hello #{params[:name]}!" end Можно также использовать именные параметры в переменных блоков: get '/hello/:name' do |n| "Hello #{n}!" end Шаблоны маршрутов также могут включать splat (wildcard, *, любая строка символов) параметры доступные в 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 Маршруты также могут использовать регулярные выражения в качестве шаблона URL: get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end Или с параметром блока: get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end === Условия Маршруты могут включать различные условия совпадений, такие как user agent: get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "You're using Songbird version #{params[:agent][0]}" end get '/foo' do # соответствует non-songbird браузерам end Другими доступными условиями являются +host_name+ и +provides+: get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end Довольно легко можно задать собственные условия: set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end === Возвращаемые значения Возвращаемое значение блока маршрута ограничивается телом ответа, которое будет передано HTTP клиенту, или следующей "прослойкой" (middleware, промежуточная программа) в Rack стеке. Чаще всего это строка, как в вышеизложенных примерах. Но и другие значения также приемлемы. Вы можете вернуть любой объект, который будет либо корректным Rack ответом, Rack телом ответа, либо кодом состояния HTTP: * Массив с тремя переменными: [status (Fixnum), headers (Hash), response body (должен отвечать на #each)] * Массив с двумя переменными: [status (Fixnum), response body (должен отвечать на #each)] * Объект, отвечающий на #each, который передает только строковые типы данных в этот блок * Fixnum, соответствующий коду состояния HTTP Таким образом мы легко можем создать поточный пример: class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } === Собственные детекторы совпадений для маршрутов Как показано выше, 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 опцию: set :public, File.dirname(__FILE__) + '/static' Учтите, что имя директории со статическими файлами не включено в URL. Например, файл ./public/css/style.css будет доступен как http://example.com/css/style.css. == Виды / Шаблоны Шаблоны по умолчанию будут использованы из директории ./views. Для использования другой директории: set :views, File.dirname(__FILE__) + '/templates' Важно помнить, что вы всегда должны указывать шаблоны с помощью символов, даже если это подкаталог (в этом случае используйте :'subdir/template'). Вы должны использовать символ, иначе методы, ответственные за рендеринг, отобразят просто переданную им строку. === Haml шаблоны haml gem/библиотека необходима для рендеринга HAML шаблонов: # Вам нужно будет подключить haml gem в приложении require 'haml' get '/' do haml :index end Отрисует ./views/index.haml. {Опции Haml}[http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options] могут быть установлены глобально через конфигурацию Sinatra, см. {Опции и Конфигурация}[http://www.sinatrarb.com/configuration.html], и переопределены локально. set :haml, :format => :html5 # :xhtml - Haml формат по умолчанию get '/' do haml :index, :format => :html4 # переопределен end === Erb шаблоны # Вам нужно будет подключить erb в приложении require 'erb' get '/' do erb :index end Отрисует ./views/index.erb. === Erubis шаблоны erubis gem/библиотека необходима для рендеринга Erubis шаблонов: # Вам нужно будет подключить Erubis в приложении require 'erubis' get '/' do erubis :index end Отрисует ./views/index.erubis. Также возможно заменить Erb на Erubis: require 'erubis' Tilt.register :erb, Tilt[:erubis] get '/' do erb :index end Отрисует ./views/index.erb с помощью Erubis. === Builder шаблоны builder gem/библиотека необходима для рендеринга builder шаблонов: # Вам нужно будет подключить builder в приложении require 'builder' get '/' do builder :index end Отрисует ./views/index.builder. === Nokogiri шаблоны nokogiri gem/библиотека необходима для рендеринга nokogiri шаблонов: # Вам нужно будет подключить nokogiri в приложении require 'nokogiri' get '/' do nokogiri :index end Отрисует ./views/index.nokogiri. === Sass шаблоны haml gem/библиотека необходима для рендеринга Sass шаблонов: # Вам нужно будет подключить haml или sass в приложении require 'sass' get '/stylesheet.css' do sass :stylesheet end Отрисует ./views/stylesheet.sass. {Опции Sass}[http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options] могут быть установлены глобально через конфигурацию Sinatra, см. {Опции и Конфигурация}[http://www.sinatrarb.com/configuration.html], и переопределены локально. set :sass, :style => :compact # :nested - стиль Sass по умолчанию get '/stylesheet.css' do sass :stylesheet, :style => :expanded # переопределен end === Scss шаблоны haml gem/библиотека необходима для рендеринга Scss шаблонов: # Вам нужно будет подключить haml или sass в приложении require 'sass' get '/stylesheet.css' do scss :stylesheet end Отрисует ./views/stylesheet.scss. {Опции Scss}[http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options] могут быть установлены глобально через конфигурацию Sinatra, см. {Опции и Конфигурация}[http://www.sinatrarb.com/configuration.html], и переопределены локально. set :scss, :style => :compact # :nested - стиль Scss по умолчанию get '/stylesheet.css' do scss :stylesheet, :style => :expanded # переопределен end === Less шаблоны less gem/библиотека необходима для рендеринга Less шаблонов: # Вам нужно будет подключить less в приложении require 'less' get '/stylesheet.css' do less :stylesheet end Отрисует ./views/stylesheet.less. === Liquid шаблоны liquid gem/библиотека необходима для рендеринга liquid шаблонов: # Вам нужно будет подключить liquid в приложении require 'liquid' get '/' do liquid :index end Отрисует ./views/index.liquid. Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме +yield+), то вы почти всегда будете передавать локальные переменные: liquid :index, :locals => { :key => 'value' } === Markdown шаблоны rdiscount gem/библиотека необходима для рендеринга Markdown шаблонов: # Вам нужно будет подключить rdiscount в приложении require "rdiscount" get '/' do markdown :index end Отрисует ./views/index.markdown (+md+ и +mkd+ также являются допустимыми файловыми расширениями). В Markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим движком рендеринга: erb :overview, :locals => { :text => markdown(:introduction) } Заметьте, что вы можете вызывать метод +markdown+ из других шаблонов: %h1 Hello From Haml! %p= markdown(:greetings) Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете использовать лэйаут-шаблоны (layouts) на Markdown. Тем не менее, есть возможность использовать один движок рендеринга для шаблона, а другой для лэйаута с помощью опции :layout_engine: get '/' do markdown :index, :layout_engine => :erb end Отрисует ./views/index.md с ./views/layout.erb в качестве лэйаута. Также вы можете задать такие опции рендеринга глобально: set :markdown, :layout_engine => :haml, :layout => :post get '/' do markdown :index end Отрисует ./views/index.md (и любой другой шаблон на Markdown) с ./views/post.haml в качестве лэйаута. Также возможно обрабатывать Markdown с помощью BlueCloth, а не RDiscount: require 'bluecloth' Tilt.register 'markdown', BlueClothTemplate Tilt.register 'mkd', BlueClothTemplate Tilt.register 'md', BlueClothTemplate get '/' do markdown :index end Отрисует ./views/index.md с помощью BlueCloth. === Textile шаблоны RedCloth gem/библиотека необходима для рендеринга Textile шаблонов: # Вам нужно будет подключить redcloth в приложении require "redcloth" get '/' do textile :index end Отрисует ./views/index.textile. В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим движком рендеринга: erb :overview, :locals => { :text => textile(:introduction) } Заметьте, что вы можете вызывать метод +textile+ из других шаблонов: %h1 Hello From Haml! %p= textile(:greetings) Вы не можете вызывать Ruby из Textile, соответственно, вы не можете использовать лэйаут-шаблоны на Textile. Тем не менее, есть возможность использовать один движок рендеринга для шаблона, а другой для лэйаута с помощью опции :layout_engine: get '/' do textile :index, :layout_engine => :erb end Отрисует ./views/index.textile с ./views/layout.erb в качестве лэйаута. Также вы можете задать такие опции рендеринга глобально: set :textile, :layout_engine => :haml, :layout => :post get '/' do textile :index end Отрисует ./views/index.textile (и любой другой шаблон на Textile) с ./views/post.haml в качестве лэйаута. === RDoc шаблоны rdoc gem/библиотека необходима для рендеринга RDoc шаблонов: # Вам нужно будет подключить rdoc/markup/to_html в приложении require "rdoc/markup/to_html" get '/' do rdoc :index end Отрисует ./views/index.rdoc. В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим движком рендеринга: erb :overview, :locals => { :text => rdoc(:introduction) } Заметьте, что вы можете вызывать метод +rdoc+ из других шаблонов: %h1 Hello From Haml! %p= rdoc(:greetings) Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать лэйаут-шаблоны на RDoc. Тем не менее, есть возможность использовать один движок рендеринга для шаблона, а другой для лэйаута с помощью опции :layout_engine: get '/' do rdoc :index, :layout_engine => :erb end Отрисует ./views/index.rdoc с ./views/layout.erb в качестве лэйаута. Также вы можете задать такие опции рендеринга глобально: set :rdoc, :layout_engine => :haml, :layout => :post get '/' do rdoc :index end Отрисует ./views/index.rdoc (и любой другой шаблон на RDoc) с ./views/post.haml в качестве лэйаута. === Radius шаблоны radius gem/библиотека необходима для рендеринга Radius шаблонов: # Вам нужно будет подключить radius в приложении require 'radius' get '/' do radius :index end Отрисует ./views/index.radius. Так как в Radius шаблоне невозможно вызывать методы из Ruby (кроме +yield+), то вы почти всегда будете передавать локальные переменные: radius :index, :locals => { :key => 'value' } === Markaby шаблоны markaby gem/библиотека необходима для рендеринга Markaby шаблонов: # Вам нужно будет подключить markaby в приложении require 'markaby' get '/' do markaby :index end Отрисует ./views/index.mab. Вы также можете использовать внутристроковые Markaby шаблоны: get '/' do markaby { h1 "Welcome!" } end === Slim шаблоны slim gem/библиотека необходима для рендеринга slim шаблонов: # Вам нужно будет подключить slim в приложении require 'slim' get '/' do slim :index end Отрисует ./views/index.slim. === CoffeeScript шаблоны Вам понадобится coffee-script gem/библиотека и что-то одно из следующего списка, чтобы запускать JavaScript: * +node+ (из Node.js) * вы можете использовать OSX (есть встроенные средства для выполнения JavaScript) * +therubyracer+ gem/библиотека Подробнее смотрите на {странице проекта}[http://github.com/josh/ruby-coffee-script]. Таким образом вы можете использовать CoffeeScript шаблоны. # Вам нужно будет подключить coffee-script в приложении require 'coffee-script' get '/application.js' do coffee :application end Отрисует ./views/application.coffee. === Встроенные шаблоны get '/' do haml '%div.title Hello World' end Отрисует встроенный (строчный) шаблон. === Доступ к переменным в шаблонах Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра, установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах: get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end Либо установите их через хеш локальных переменных: get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.name', :locals => { :foo => foo } end Это обычный подход, когда шаблоны рендерятся как частные (partials) из других шаблонов. === Вложенные шаблоны Шаблоны также могут быть определены в конце файла-исходника: require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world!!!!! Заметьте: вложенные шаблоны, определенные в файле-исходнике, который подключил Sinatra, будут автоматически загружены. Вызовите enable :inline_templates напрямую, если у вас вложенные шаблоны в других файлах. === Именные шаблоны Шаблоны также могут быть определены, используя template метод: template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end Если шаблон с именем "layout" существует, то он будет использован каждый раз, когда шаблоны будут отрисовываться. Вы можете отключать лэйаут в каждом конкретном случае с помощью :layout => false или отключить его для всего приложения, например, так: set :haml, :layout => false: get '/' do haml :index, :layout => !request.xhr? end === Привязка файловых расширений Чтобы связать расширение файла и движок рендеринга, используйте Tilt.register. Например, если вы хотите использовать расширение +tt+ для шаблонов Textile: Tilt.register :tt, Tilt[:textile] === Добавление собственного движка рендеринга Сначала зарегистрируйте свой движок в Tilt, затем создайте метод, отвечающий за отрисовку: Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end Отрисует ./views/index.myat. Чтобы узнать больше о Tilt, смотрите https://github.com/rtomayko/tilt == Фильтры +before+-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты. Фильтры могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах: before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end +after+-фильтры выполняются после каждого запроса в том же контексте, что и пути. Фильтры могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в +before+-фильтрах и маршрутах, будут доступны в +after+-фильтрах: after do puts response.status end Заметьте: если вы используете метод +body+, а не просто возвращаете строку из маршрута, то тело ответа не будет доступно в +after+-фильтрах, потому что оно будет сгенерировано позднее. Фильтры могут использовать шаблоны URL и будут интерпретированы, только если путь запроса совпадет с этим шаблоном: before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end Как и маршруты, фильтры могут использовать условия: before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end == Методы-помощники Используйте метод helpers, чтобы определить методы-помощники, которые в дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах: helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end === Использование сессий Сессия используется, чтобы сохранять состояние между запросами. Если эта опция включена, то у вас будет один хэш сессии на одну пользовательскую сессию: enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end Заметьте, что при использовании enable :sessions все данные сохраняются в куки. Это может быть не совсем то, что вы хотите (например, сохранение больших объемов данных увеличит ваш трафик). В таком случае вы можете использовать альтернативную Rack "прослойку" (middleware), реализующую механизм сессий. Для этого *не надо* вызывать enable :sessions, вместо этого следует подключить ее также как и любую другую "прослойку": use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end Для повышения безопасности данные сессии в куках подписываются секретным ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ будет меняться с каждым запуском приложения, вы, возможно, захотите установить ключ вручную, чтобы у всех экземпляров вашего приложения быд один и тот же ключ: set :session_secret, 'super secret' Если вы хотите больше настроек для сессий, вы можете задать их, используя хэш с опциями и параметр +sessions+: set :sessions, :domain => 'foo.com' === Прерывание Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте: 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 request.env.merge("PATH_INFO" => '/bar') [status, body.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+, вызванные без аргументов, возвращают свои текущие значения. === Mime-типы Когда вы используете send_file или статические файлы, у вас могут быть mime-типы, которые Sinatra не понимает по умолчанию. Используйте +mime_type+ для их регистрации по расширению файла: mime_type :foo, 'text/foo' Вы также можете использовать это в +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://google.com', 'wrong place, buddy' Вы также можете перенаправить пользователя обратно, на страницу с которой он пришел, с помощью redirect back: get '/foo' do "do something" end get '/bar' do do_something redirect back end Чтобы передать какие-либо параметры вместе с перенаправлением, либо добавьте их в строку запроса: redirect to('/bar?sum=42') Либо используйте сессию: enable :session 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}[http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation]: etag @article.sha1, :weak Эти методы-помощники не станут ничего кэшировать для вас, но они дадут необходимую информацию для вашего кэша. Если вы ищите легкое решение для кэширования, попробуйте {rack-cache}[http://rtomayko.github.com/rack-cache/]: require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end === Отправка файлов Для отправки файлов пользователю вы можете использовать метод 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, по умолчанию: размер файла. Этот метод будет использовать возможности Rack сервера для отправки файлов, если они доступны, а в противном случае, будет напрямую отдавать файл из Ruby процесса. Метод send_file также обеспечивает автоматическую обработку частичных (range) запросов с помощью Sinatra. === Доспут к объекту запроса Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) с помощью request метода: # приложение запущено на http://example.com/example get '/foo' do 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_HEADER"] # значение заголовка SOME_HEADER request.referrer # источник запроса клиента либо '/' request.user_agent # user agent (используется для :agent условия) request.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 === Поиск шаблонов Для поиска шаблонов и их последующего рендеринга используется метод 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 1, :b => 2 # то же самое, что и `set :option, true` enable :option # то же самое, что и `set :option, false` disable :option # у вас могут быть "динамические" опции с блоками set(:css_dir) { File.join(views, 'css') } end Будет запущено, когда окружение (RACK_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 === Доступные настройки [absolute_redirects] если отключено, то Sinatra будет позволять использование относительных перенаправлений, тем не менее, Sinatra перестанет соответствовать RFC 2616 (HTTP 1.1), который разрешает только абсолютные перенаправления. Включайте эту опцию, если ваше приложение работает за обратным прокси, который настроен не совсем корректно. Обратите внимание, метод +url+ все равно будет генерировать абсолютные URL, если вы не передадите +false+ вторым аргументом. Отключено по умолчанию. [add_charsets] mime-типы, к которым метод content_type будет автоматически добавлять информацию о кодировке. Вам следует добавлять значения к этой опции вместо ее переопределения: settings.add_charsets << "application/foobar" [app_file] главный файл приложения, используется для определения корневой директории проекта, директорий с шаблонами и статическими файлами, вложенных шаблонов. [bind] используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только встроенным сервером. [default_encoding] кодировка, если неизвестна (по умолчанию: "utf-8"). [dump_errors] отображать ошибки в логе. [environment] текущее окружение, по умолчанию, значение ENV['RACK_ENV'] или "development", если ENV['RACK_ENV'] не доступна. [logging] использовать логгер. [lock] создает блокировку для каждого запроса, которая гарантирует обработку только одного запроса в текущий момент времени в Ruby процессе. Включайте, если ваше приложение не потоко-безопасно (thread-safe). Отключено по умолчанию. [method_override] использовать "магический" параметр _method, чтобы позволить использование PUT/DELETE форм в браузерах, которые не поддерживают эти методы. [port] порт, на котором будет работать сервер. Используется только встроенным сервером. [prefixed_redirects] добавлять или нет параметр request.script_name к редиректам, если не задан абсолютный путь. Таким образом redirect '/foo' будет вести себя как redirect to('/foo'). Отключено по умолчанию. [public] директория, откуда будут раздаваться статические файлы. [reload_templates] перезагружать или нет шаблоны на каждый запрос. Включено в режиме разработки. [root] корневая директория проекта. [raise_errors] возбуждать исключения (будет останавливать приложение). [run] если включено, Sinatra будет самостоятельно запускать веб-сервер. Не включайте, если используете rackup или аналогичные средства. [running] работает ли сейчас встроенный сервер? Не меняйте эту опцию! [server] сервер или список серверов, которые следует использовать в качестве встроенного сервера. По умолчанию: ['thin', 'mongrel', 'webrick'], порядок задает приоритет. [sessions] включить сессии на основе кук (cookie). [show_exceptions] показывать стек вызовов (stack trace) в браузере. [static] должен ли Sinatra осуществлять раздачу статических файлов. Отключите, когда используете какой-либо веб-сервер для этой цели. Отключение значительно улучшит производительность приложения. Включено по умолчанию. [views] директория с шаблонами. == Обработка ошибок Обработчики ошибок исполняются в том же контексте, что и маршруты, +before+-фильтры, а это означает, что всякие прелести вроде haml, erb, halt и т.д. доступны и им. === NotFound Когда возбуждено исключение Sinatra::NotFound, или кодом ответа является 404, то будет вызван not_found обработчик: not_found do 'This is nowhere to be found.' end === Ошибки Обработчик ошибок +error+ будет вызван, когда исключение возбуждено из блока маршрута, либо из фильтра. Объект-исключение доступен как переменная sinatra.error в Rack: error do 'Sorry there was a nasty error - ' + env['sinatra.error'].name end Частные ошибки: error MyCustomError do 'So what happened was...' + request.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[http://rack.rubyforge.org/], минимальный стандартный интерфейс для веб-фреймворков на 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[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] DSL (чаще всего используется в rackup файлах). Например, +use+ метод принимает множественные переменные, также как и блоки: use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end Rack распространяется с различными стандартными "прослойками" для логирования, отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra использует многие из этих компонентов автоматически, основываясь на конфигурации, чтобы вам не приходилось регистрировать/использовать (+use+) их вручную. == Тестирование Тесты для Sinatra приложений могут быть написаны с помощью библиотек, фреймворков, поддерживающих тестирование Rack. {Rack::Test}[http://gitrdoc.com/brynary/rack-test] рекомендован: require 'my_sinatra_app' require 'test/unit' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end Обратите внимание: Встроенные модуль Sinatra::Test и класс Sinatra::TestHarness являются устаревшими, начиная с релиза 0.9.2. == Sinatra::Base — "прослойки", библиотеки и модульные приложения Описание своего приложения самым простейшим способом (с помощью DSL верхнего уровня, как в примерах выше) работает отлично для крохотных приложений, но имеет множество недостатков, когда надо создать компоненты, такие как Rack middleware ("прослойки"), Rails metal, простые библиотеки с серверными компонентами, расширения Sinatra. DSL верхнего уровня загрязняет пространство имен Object и подразумевает стиль конфигурации микро-приложения (например, единый файл приложения, ./public и ./views директории, создание логов, страницу деталей об исключениях и т.д.). И тут на помощь приходит Sinatra::Base: require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end Методы, доступные Sinatra::Base сабклассам идентичны тем, что доступны в DSL верхнего уровня. Большинство приложений верхнего уровня могут быть конвертированы в Sinatra::Base компоненты с помощью двух модификаций: * Вы должны подключать sinatra/base вместо +sinatra+, иначе все методы предоставляемые Sinatra будут импортированы в глобальное пространство имен. * Поместите все маршруты, обработчики ошибок, фильтры и опции в сабкласс Sinatra::Base. Sinatra::Base — это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены. Смотрите {Опции и Конфигурация}[http://www.sinatrarb.com/configuration.html] для детальной информации об опциях и их поведении. === Модульные приложения против классических Вопреки всеобщему убеждению в классическом стиле (самом простом) нет ничего плохого. Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное приложение. У классического стиля есть всего два недостатка относительно модульного: * У вас может быть только одно приложение Sinatra на Ruby процесс. Если вы планируете использовать больше, то переключайтесь на модульный стиль. * Приложения, использующие классический стиль, добавляют методы к Object. Если вы планируете поставлять свое приложение в виде библиотеки/gem, то переключайтесь на модульный стиль. Не существует причин, по которым вы не могли бы смешивать модульный и классический стили. Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках: Опция Классический Модульный app_file файл с приложением nil run $0 == app_file false logging true false method_override true false inline_templates true false === Запуск модульных приложений Есть два общепринятых способа запускать модульные приложения: запуск напрямую с помощью run!: # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... здесь код приложения ... # запускаем сервер, если исполняется текущий файл run! if app_file == $0 end И запускаем с помощью: ruby my_app.rb Или с помощью конфигурационного файла config.ru, который позволяет использовать любой Rack-совместимый сервер приложений. # config.ru require 'my_app' run MyApp Запускаем: rackup -p 4567 === Запуск классических приложений с 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 эндпоинту в качестве "прослойки". Этим эндпоинтом может быть другое Sinatra приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/...): require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] = 'admin' and 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 login." end end get('/') { "Hello #{session['user_name']}." } end == Области видимости и привязка Текущая область видимости определяет методы и переменные, доступные в данный момент. === Область видимости приложения / класса Любое 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+ Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами: * объект, переданный блокам конфигурации (configure { |c| ... }) * +settings+ внутри области видимости запроса === Область видимости запроса/экземпляра Для каждого входящего запроса будет создан новый экземпляр вашего приложения, и все блоки обработчика будут запущены в этом контексте. В этой области видимости вам доступны +request+ и +session+ объекты, вызовы методов рендеринга, такие как +erb+ или +haml+. Вы можете получить доступ к области видимости приложения из контекста запроса, используя помощник +settings+: class MyApp < Sinatra::Base # Я в области видимости приложения! get '/define_route/:name' do # Область видимости запроса '/define_route/:name' @value = 42 settings.get("/#{params[:name]}") do # Область видимости запроса "/#{params[:name]}" @value # => nil (другой запрос) end "Route defined!" end end У вас будет область видимости запроса внутри: * get/head/post/put/delete/options блоков * before/after фильтрах * методах помощниках * шаблонах/видах === Область видимости делегирования Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, оно не полностью на 100% ведет себя как область видимости класса, так как у вас нету привязки к классу: только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой +self+ объект). Вы можете непосредственно добавить методы делегирования, используя Sinatra::Delegator.delegate :method_name. У вас будет контекст делегирования внутри: * Привязки верхнего уровня, если вы сделали require "sinatra" * Объекта, расширенного с помощью примеси Sinatra::Delegator Посмотрите сами в код: тут {Sinatra::Delegator примесь}[http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128] будет {включена в глобальное пространство имен}[http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28]. == Командная строка Sinatra приложения могут быть запущены напрямую: ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] Опции включают: -h # помощь -p # настроить порт (по умолчанию 4567) -o # настроить хост (по умолчанию 0.0.0.0) -e # настроить окружение, режим (по умолчанию development) -s # настроить rack сервер/обработчик (по умолчанию thin) -x # включить мьютекс (по умолчанию выключен) == Системные требования Следующие версии Ruby официально поддерживаются: [ Ruby 1.8.7 ] 1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или Rubinius. [ Ruby 1.9.2 ] 1.9.2 поддерживается и рекомендована. Заметьте, что Radius и Markaby пока несовместимы с 1.9.2. Не используйте 1.9.2p0, известно, что эта версия весьма не стабильна при использовании Sinatra. [ Rubinius ] Rubinius официально поддерживается (Rubinius >= 1.2.3), все, включая все языки шаблонов, работает. [ JRuby ] JRuby официально поддерживается (JRuby >= 1.6.0). Нет никаких проблем с использованием альтернативных шаблонов. Тем не менее, если вы выбираете JRuby, то, пожалуйста, посмотрите на JRuby Rack-сервера, так как Thin не поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все еще экспериментальная, что на данный момент затрагивает только RDiscount. Ruby 1.8.6 больше не поддерживается. Мы также следим за новыми версиями Ruby. Следующие реализации Ruby не поддерживаются официально, но известно, что на них запускается Sinatra: * Старые версии JRuby и Rubinius * MacRuby, Maglev, IronRuby * Ruby 1.9.0 и 1.9.1 * Ruby 1.8.6 с помощью {backports}[https://github.com/marcandre/backports/#readme] То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает, то мы предполагаем, что это не наша проблема, а их. Мы также запускаем наши CI-тесты на последней версии Ruby (предстоящий 1.9.3), но мы не можем ничего гарантировать, так как она постоянно развивается. Предполагается, что 1.9.3p0 будет поддерживаться. Sinatra должен работать на любой операционной системе, поддерживаемой выбранной реализацией Ruby. == На острие Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать свое приложение вместе с master бранчем Sinatra, он весьма стабилен. Мы также время от времени выпускаем предварительные версии, так что вы можете делать так: gem install sinatra --pre Чтобы воспользоваться некоторыми самыми последними возможностям. === С помощью Bundler Если вы хотите запускать свое приложение с последней версией Sinatra, то рекомендуем использовать {Bundler}[http://gembundler.com/]. Сначала установите Bundler, если у вас его еще нет: gem install bundler Затем создайте файл +Gemfile+ в директории вашего проекта: source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # другие зависимости gem 'haml' # например, если используете haml gem 'activerecord', '~> 3.0' # может быть, вам нужен и ActiveRecord 3.x Обратите внимание, вам нужно будет указывать все зависимости вашего приложения в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler автоматически скачает и добавит. Теперь вы можете запускать свое приложение примерно так: bundle exec ruby myapp.rb === Вручную Создайте локальный клон репозитория и запускайте свое приложение с sinatra/lib директорией в $LOAD_PATH: cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb Чтобы обновить исходники Sinatra: cd myapp/sinatra git pull === Установка глобально Вы можете самостоятельно собрать gem: git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install Если вы устанавливаете пакеты (gem) от пользователя root, то вашим последним шагом должна быть команда sudo rake install == Версии Sinatra использует {Semantic Versioning}[http://semver.org/], SemVer и SemVerTag. == Дальнейшее чтение * {Вебсайт проекта}[http://www.sinatrarb.com/] - Дополнительная документация, новости и ссылки на другие ресурсы. * {Участие}[http://www.sinatrarb.com/contributing] - Нашли баг? Нужна помощь? Написали патч? * {Слежение за проблемами}[http://github.com/sinatra/sinatra/issues] * {Twitter}[http://twitter.com/sinatra] * {Лист рассылки}[http://groups.google.com/group/sinatrarb/topics] * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net