Что за Rack?
По словам Christian Neukirchen — автора гема rack — он создан для обеспечения минимального API для подключения веб-серверов, поддерживающих Ruby (WEBrick, Mongrel и т.д.) и ruby веб-фреймворками (Rails, Sinatra и др.).
Такие фреймворки, как например Sinatra, построены над Rack или у них есть rack-интерфейс, позволяющий серверам веб-приложений подключаться к ним.
Цель rack проста — он позволяет с легкостью обрабатывать HTTP-запросы.
HTTP — это простой протокол: в основном описывается формат данных, которые отправляются на сервер и которые возвращаются в клиенту. HTTP-запрос и HTTP-ответ имеют очень похожую структуру. HTTP-запрос — тройка, состоящая из пары метод-ресурс, набора заголовков и тела запроса, в то время как HTTP-ответ HTTP состоит из кода ответа, набора заголовков и опционального тела ответа.
Мэппинг Rack близок к этому. Rack-приложение — это ruby-объект, у которого есть метод call, принимающий единственный аргумент — environment, и возвращающий массив трех элементов: статус, заголовки и тело ответа.
Rack включает в себя обработчики и адаптеры. Первые соединяют его с ruby веб-серверами, вторые — связывают с фреймворками.
Используя middleware, можно изменить rack под нужды вашего приложения. Основная идея rack middleware — обработка запроса до того, как он попал в приложение, и обработка ответа перед возвратом его клиенту.
Документация
rack.rubyforge.org/doc
Установка Rack
Прошу заметить, что все, о чем говорится в этой статье, протестировано на ruby 1.9.2 установленным на Windows (хозяин — барин — прим. пер.).
Проверим, установлен ли у вас rack. Для этого откройте консоль:
# irb --simple-prompt
>> require 'rack'
=> true
Если require вернул true, это значит, что rack установлен. В противном случае вы получите сообщение об ошибке типа:
LoadError: no such file to load -- rack
Исправляется это просто командой gem install rack.
Быстрый экскурс в объект proc
Помните объект proc? В руби блоки — это не объекты, но они могут быть преобразованы в объекты класса Proc. Это можно сделать, вызвав метод lambda класса Object. Блок, созданный с помощью lambda ведет себя как метод ruby. А у класса Proc есть метод call, который вызывает блок на исполнение.
>> my_rack_proc = lambda {puts 'Rack Intro'}
=> #<Proc:0x1fc9038@(irb):2(lambda)>
>> # method call invokes the block
?> my_rack_proc.call
Rack Intro
=> nil
Простейшее rack-приложение — my_rack_proc
Как уже упоминалось, наше rack приложение — это объект (не класс), реагирующий на метод call и принимающий один аргумент — окружение (он же environment — прим. пер.). Окружение должно быть экземпляром класса Hash. Приложение должно возвращать массив трех значений: код состояния (больший или равный 100), заголовки (тоже хэш) и тела (тело обычно является массивом строк, экземпляром приложения или файлом. Тело ответа должно отзываться на метод each и выводить только строковые значения). Создадим наш новый proc объект.
>> my_rack_proc = lambda { |env| [200, {}, ["Hello. The time is #{Time.now}"]] }
=> # <Proc:0x1f4c358@(irb):5(lambda)>
Теперь можно вызвать my_rack_proc:
>> my_rack_proc.call({})
=> [200, {}, ["Hello. The time is 2011-10-24 09:18:56 +0530"]]
my_rack_proc — наш однострочный Rack.
В приведенном выше примере мы использовали пустой хеш для заголовков. Вместо этого, давайте что-то в заголовке следующим образом:
>> my_rack_proc = lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
=> #<Proc:0x1f4c358@(irb):5(lambda)>
>> my_rack_proc.call({})
=> [200, {"Content-Type" => "text/plain"}, ["Hello. The time is 2011-10-24 09:18:56 +0530"]]
Мы можем запустить наше rack-приложение с использованием любого из обработчиков.
Посмотрим, какие типы обработчиков нам доступны:
>> Rack::Handler.constants
=> [:CGI, :FastCGI, :Mongrel, :EventedMongrel, :SwiftipliedMongrel, :WEBrick, :LSWS, :SCGI, :Thin]
Чтобы использовать WEBrick (по умолчанию WEBrick — сервер устанавливающийся вместе с Ruby), введите:
>> Rack::Handler::WEBrick
=> Rack::Handler::WEBrick
Все эти обработчики имеют метод run, запускающий rack-приложение:
>> Rack::Handler::WEBrick.run my_rack_proc
[2011-10-24 10:00:45] INFO WEBrick 1.3.1
[2011-10-24 10:00:45] INFO ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 10:00:45] INFO WEBrick::HTTPServer#start: pid=1788 port=80
Откройте в браузере страничку localhost и вы должны увидеть строчку типа:
Hello. The time is 2011-10-24 10:02:20 +0530
Примечание: Если у вас уже есть что-то работает на порту 80, можно запустить приложение и на другом порту.
>> Rack::Handler::WEBrick.run my_rack_proc, :Port => 9876
[2011-10-24 11:32:21] INFO WEBrick 1.3.1
[2011-10-24 11:32:21] INFO ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 11:32:21] INFO WEBrick::HTTPServer # start: pid = 480 port = 9876
Еще одно rack-приложение — my_method
Rack-application — это не обязательно lambda. Оно так же может являтся методом
>> def my_method env
>> [200, {}, ["method called"]]
>> end
=> nil
>> Rack::Handler::WEBrick.run method(:my_method)
[2011-10-24 14:32:05] INFO WEBrick 1.3.1
[2011-10-24 14:32:05] INFO ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 14:32:05] INFO WEBrick::HTTPServer#start: pid=1644 port=80
По адресу localhost вы снова увидите:
method called
Объекты методов создаются с помощью Object#method. Они связаны с конкретным объектом (а не только с классом). Они могут быть использованы для вызова метода в пределах объекта. Method.call — это метод, вызывающий метод (Ох, руби, руби… — прим. пер.) с указанными аргументами и возвращающий значение, которое возвращает вызываемый метод.
Использование rackup
Гем rack идет с пачкой полезных штук, упрощающий жизнь разработчика. Одной из этих штук является rackup.
Rackup — полезный инструмент для запуска rack-приложений. Rackup сам определяет среду, в которой он запущен и запускает ваше приложение как FastCGI, CGI, или автономно с Mongrel или WEBrick — всех с одной и той де конфигурацией.
Для использования rackup, необходимо создать его конфигурационный файл. По соглашению необходимо использовать расширение .ru у этого конфига. По умолчанию, rackup стартует на порту 9292.
Давайте создадим файл config.ru, содержащий:
run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
Этот файл содержит слово run, который может быть вызван всем, что отвечает на метод .call.
Для запуска rack-приложения в папке с файлом config.ru введите:
$ rackup config.ru
[2011-10-24 15:18:03] INFO WEBrick 1.3.1
[2011-10-24 15:18:03] INFO ruby 1.9.2 (2011-07-09) [i386-mingw32]
[2011-10-24 15:18:03] INFO WEBrick::HTTPServer # start: pid = 3304 port = 9292
Теперь по адресу localhost:9292/ нам будет снова будет отдаваться следующая страничка:
Hello. The time is 2011-10-24 15:18:10 +0530
Давайте переместим наше приложение из файла config.ru в my_app.rb:
# my_app.rb
class MyApp
def call(env)
[200, {"Content-Type" => "text/html"}, ["Hello Rack Participants"]]
end
end
Так же изменим config.ru:
require './my_app'
run MyApp.new
Теперь снова можно запустить приложение (с помощью «rackup config.ru») и проверить, отдается ли страничка браузеру.
Разворачиваем rack-приложение на Heroku.
(Данный кусок статьи был безжалостно вырезан мной, так как описывает установку git на windows и деплой на heroku, что к самой статье никакого отношения не имеет. Если вам это интересно — можете прочитать в оригинале — прим. пер.)
Использование rack middleware
Ранее упоминалось, что между сервером и фреймворком rack может быть настроен для ваших нужд с использованием middleware.
Rackup тоже использует метод, принимающий middleware. Давайте используем один из встроенный в rack middleware.
Должно быть вы заметили, что каждый раз после изменения файлов config.ru или my_app.rb необходимо перезагружать сервер WEBrick. Этого можно избежать с использованием middleware Rack::Reloader. Отредактируйте config.ru:
require './my_app'
use Rack::Reloader
run MyApp.new
И my_app.rb:
# my_app.rb
class MyApp
def call(env)
[200, {"Content-Type" => "text/html"}, ["Hello Rack Participants from across the globe"]]
end
end
Запустите приложение и откройте в браузере страничку с ним. Теперь измените текст с “Hello Rack Participants from across the globe” на “Hello Rack Participants from across the world!". После обновления странички в браузере вы увидите отредактированный текст без перезапуска веб-сервера.
Написание собственного Middleware
Middleware всегда на практике служит для обертки вашего внутреннего приложения.
Создадим простейший middleware, добавляющий текст к телу http-ответа. Для этого создадим класс MyRackMiddleware:
class MyRackMiddleware
def initialize(appl)
@appl = appl
end
def call(env)
end
end
В коде выше, чтобы получить первоначальное тело ответа, мы инициализируем класс MyRackMiddleware, передавая ему приложение (appl).
Теперь изменим метод .call:
def call(env)
status, headers, body = @appl.call(env) # we now call the inner application
end
В приведенном коде, у нас есть досуп к исходному телу ответа, которое мы можем теперь изменить. Rack не гарантирует вам, что тело будет строкой. Оно так же может быть объектом, но мы знаем, что при вызове метода .each, все возвращаемое будет строкой. Используем это в методе .call:
def call(env)
status, headers, body = @appl.call(env)
append_s = "... greetings from RubyLearning!!"
[status, headers, body << append_s]
end
Теперь поменяем файл config.ru:
require './my_app'
require './myrackmiddleware'
use Rack::Reloader
use MyRackMiddleware
run MyApp.new
Запускаем приложение и в браузере можно увиеть «Hello Rack Participants from across the globe… greetings from RubyLearning!!»
Rack и Синатра
Напоследок, приведем простейший пример использования rack вместе с фреймворком sinatra.
Создадим тривиальное приложение my_sinatra.rb, которое будет использовать rack middleware:
require 'sinatra'
require './rackmiddleware'
use RackMiddleware
get '/' do
'Welcome to all'
end
Давайте теперь напшем простой middleware класс, который будет записывать в консоль время на обработку запроса у приложения. Создадим файл rackmiddleware.rb:
class RackMiddleware
def initialize(appl)
@appl = appl
end
def call(env)
start = Time.now
status, headers, body = @appl.call(env) # call our Sinatra app
stop = Time.now
puts "Response Time: #{stop-start}" # display on console
[status, headers, body]
end
end
Теперь изменим файл config.ru:
require "./my_sinatra"
run Sinatra::Application
(далее снова идет блок текста о том, как деплоить на хероку, использовать git и т.д. Не думаю, что вам это будет интересно — прим. пер.)
Теперь после запуска приложения на каждый запрос в консоль будет писаться строка с приблизительно следующим содержанием:
?[36m2011-10-26T05:40:06+00:00 app[web.1]:?[0m *** Response Time ***: 0.000297385
Вот и все. Надеюсь, вам понравилась это вступление в rack. Happy Racking!