Дружимся с Rack #2: Rack::Builder
октября 25, 2010 | Published in Rack | 9 Comments
В первой части мы использовали rackup для того, чтобы сделать возможным выбор порта и сервера для запуска rack-приложения. Rackup’у мы предоставляли файл конфигурации следующего содержимого:
# config.ru run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
Глубоко в своих недрах rackup преобразует ваш скрипт конфигурирования в экземпляр Rack::Builder. А теперь о том, что же такое Rack::Builder?
Что же такое Rack::Builder?
Rack::Builder реализует маленький DSL (в переводе язык предметной области или что-то типа того) для многократного построения Rack приложений.
- Документация по Rack API
Rack::Builder это такая штука, которая склеивает различные Rack Middleware и приложения вместе и преобразует это месиво в то, что носит гордое название Rack Application. Rack::Build можно представить как коктейль изготовленный методом build — наслоением одного составляющего над другим.
Давайте сообщим Rack о том, что приложение infinity следует запускать на базе сервера WEBrick, по 9292 порту:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} Rack::Handler::WEBrick.run infinity, :Port => 9292
Все экземпляры infinity посылают хэш env преобразованный в человекопонятную строку назад в браузер.
Ниже представлены три основных метода из Rack::Builder о которых вы должны обязательно знать:
1. Rack::Builder#run
Rack::Builder#run определяет актуальное приложение Rack, с которым будет работать Rack::Builder.
Преобразовываем infinity для использования с Rack::Builder:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new builder.run infinity Rack::Handler::WEBrick.run builder, :Port => 9292
Согласно соглашению принятому в Ruby-сообществе следует использовать блоковую форму (это не правило, а рекомендация) Rack::Builder:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new do run infinity end Rack::Handler::Mongrel.run builder, :Port => 9292
2.Rack::Builder#use
Rack::Builder#use добавляет middleware к стеку rack приложения созданному спомощью Rack::Builder (помните коктейль?). Если термин middleware вводит вас в замешательство или вызывает острые боли внизу спины, не беспокойтсь, в следующем посте я расскажу что это такое и чем middleware отличается от rack приложения. Придерживайтесь пока аналогии с before/after/around фильтрами в Ruby on Rails.
Rack имеет множество полезных middleware и одно из них это Rack::CommonLogger, что занимается тем, что записывает одну строку в log-файл в стандартном формате веб-сервера Apache.
Добавляем middleware Rack::CommonLogger к нашему infinity:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new do use Rack::CommonLogger run infinity end Rack::Handler::WEBrick.run builder, :Port => 9292
Что нас здесь интересует, так это конечно же строка use Rack::CommonLogger. Так как мы не указываем Rack::CommonLogger’у, куда конкретно необходимо записывать логи, то он, по умолчанию, будет вести запись в env["rack.errors"], то есть выводить все ошибки в консоль.
3. Rack::Builder#map
Rack::Builder#map устанавливает стек для rack приложений/middleware указанный путь или URI и все дочерние пути.
Давайте представим что вы хотите показать текст «infinity 0.1″ на всех страницах в /version (/version, /version/…, но не /version…) вы можете хотеть сделать что-то похожее на это:
require 'rubygems' require 'rack' infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new do use Rack::CommonLogger run infinity map '/version' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity 0.1"]] } end end Rack::Handler::WEBrick.run builder, :Port => 9292
К сожалению этот код нерабочий. Точнее он работает, но возвращает ошибку, а не то, что нам нужно. Дело в том, что метод Rack::Builder#map инкапсулирует одно пространство (область действия), и на каждое такое пространство положен только один метод Rack::Builder#run. В примере выше, мы запустили infinity в высшем, глобальном пространстве и map ‘/version’ является вложенным пространством, которое также имеет метод run, таким образом мы получаем два метода run в глобальном пространстве, из-за чего и возникает конфликт.
Рецепт лечения:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new do use Rack::CommonLogger map '/' do run infinity end map '/version' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity 0.1"]] } end end Rack::Handler::WEBrick.run builder, :Port => 9292
Теперь если вы перейдете по адресу http://localhsot:9292/version или http://localhost:9292/version/1 или даже http://localhost:9292/version/whatewer/doesnt/matter, вы увидите «infinity 0.1″ и для всех URI не начинающихся с /version, например в корне структуры приложени (http://localhost:9292) — вы увидите хэш env представленный в виде строки.
Важно запомнить, что:
- /versionsomething не будет показывать версию, а будет показывать хэш env.
- Когда вы работаете с большим количеством map, вы должны следовать от адресов высшего порядка к адресам низшего. Если это правило не будет соблюдаться, то адреса высшего порядка подчинят себе адреса низшего (можно сказать, что перезапишут, или аннулируют).
Вложенные блоки map
Давайте представим что вам необходимо добавить информацию о последней версии приложения «infinity beta 0.2″ по адресу /version/last, и напишем для реализации сей чудесной функции следующий код:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new do use Rack::CommonLogger map '/' do run infinity end map '/version' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity 0.1"]] } end map '/version/last' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity beta 0.2"]] } end end Rack::Handler::WEBrick.run builder, :Port => 9292
Вышеприведенный код будет работать отлично. Как и ожидалось вы увидите «infinity beta 0.2″ по адресу http://localhost:9292/version/last и «infinity 0.1″ по адресу http://localhost:9292/version.
Но, по моему скромному мнению, лучшим способом будет написать этот код с использованием вложенных блоков map:
infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} builder = Rack::Builder.new do use Rack::CommonLogger map '/' do run infinity end map '/version' do map '/' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity 0.1"]] } end map '/last' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity beta 0.2"]] } end end end Rack::Handler::WEBrick.run builder, :Port => 9292
Этот код настолько прекрасен, что хочется заняться с ним любовью! Стоит заметить, что для вложенных блоков map следует использовать вложенный, а не абсолютный URI (здесь автор оригинала статьи демонстрирует прекрасное владение навыками КО)
Rack::Builder -> rackup
Как я упоминал выше, rackup преобразует предоставляемый ему файл конфигурации в экземпляр Rack::Builder. Сейчас я покажу, что находится «под капотом» у rackup:
config_file = File.read(config) rack_application = eval("Rack::Builder.new { #{config_file} }")
После этого преобразования rackup запускает rack — приложение на соответствующем сервере:
server.run rack_application, options
Все очень просто! Если принять во внимание все, что вы только что узнали о работе rackup, то вы легко можете преобразовать наш учебный пример в файл конфигурации для rackup:
# infinity.ru infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, [env.inspect]]} use Rack::CommonLogger map '/' do run infinity end map '/version' do map '/' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity 0.1"]] } end map '/last' do run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["infinity beta 0.0"]] } end end
и теперь запустим его:
$ rackup infinity.ru
Оригинал статьи:
октября 25, 2010 at 13:59 (#)
Большое спасибо вам за статьи !
Мне не совсем понятно какую роль Rack выполняет в контексте работы приложения. Может есть пример его использования как части приложения (какая то история из жизни)? Спасибо.
октября 25, 2010 at 15:42 (#)
ggenikus, Rack выполняет роль связующего приложения с веб сервером. Собственно ничего не мешает вам самому реализовать это взаимодействие, но тогда в приложение придется вносить изменения различные при переходе на другой сервер и т.д. rack всю эту суету берет на себя. Rails и Sinatra — два самых популярных фреймворка на Ruby (а вместе с ними еще вагон и тележка) работают на базе rack.
Изучение работы с rack полезно чисто в общеобразовательных целях, либо в случае, когда вы хотите написать собственный фреймворк или приложение (без использования фреймворков, якобы для лучшей производительности).
октября 25, 2010 at 15:49 (#)
Спасибо, теперь стало понятнее. Понял глупость своего прошлого вопроса )
февраля 27, 2011 at 05:34 (#)
А нет ли где-нибудь описания работы Rack с сессиями?
февраля 27, 2011 at 13:58 (#)
Иван, к сожалению не находил, по Rack вообще статей очень мало, а в код лезть пока нет желания, да и потребности тоже. Если что-то найду, то обязательно переведу и опубликую.
марта 1, 2011 at 00:41 (#)
Подскажите пожалуйста, может вы еще знаете где можно найти информацию про Rack?
Я просто хочу сделать небольшой сайт с базовым CRUD использую sqlite или MySQL, ну и конечно стандартные web формы. Хотел бы использовать именно Rack, а не Rails. Ваш сайт выдается чуть ли не на первом месте в поисковике по запросу Rack. Я конечно буду пробовать искать информацию на офиц. сайте Rack, но может вы еще что-то знаете?
марта 1, 2011 at 15:49 (#)
Кирилл, Rack сам по себе достаточно мало кто использует и статей по нему соответственно совсем мало, да и изучать там особо не много. Я бы рекоментовал вам фреймворк Sinatra или еще более простой — Camping.
А чем Rails не устраивает вас?
марта 3, 2011 at 11:45 (#)
admin, я пытаюсь устроится на работу RoR программиста и попутно изучаю ruby и его фрэймворки, от базового к более сложному. Вы правы, я попробовал Sinatra + Datamapper и у меня получилось сделать то, что хотел. Изучение Rack пока отложу, то там надо все самому писать =)
Большое спасибо вам за ваш сайт, в ближайшее время постараюсь изучить как можно больше статей здесь. Статья про Git тоже очень понравилась ваше.
Продолжайте развивать ваш сайт даль, ведь ruby набирает популярность =)
марта 3, 2011 at 12:12 (#)
И вам спасибо за спасибо!