Быстрое вступление в rack

Original author: Satish Talim
  • Translation

Что за 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!
Share post

Similar posts

Comments 17

    –2
    «позволяюзий» поправьте пожалуйста.
      +4
      Спасибо, поправил.
      +1
      Очень быстрое вступление и короткое :) А за развернутую статью огромное спасибо — она как раз вовремя!
        +1
        Какого рода задачи решаются и какие приложения можно делать с помощью Rack? Несколько раз встречал описание rack, но так и не пришло в голову где можно это применить.
          +1
          Ну вот лично я использовал однажды, когда необходимо было, чтобы к категории в магазине можно было перейти как по ссылке /category/23 так и по любой другой хранящейся в базе ссылке (чтобы администратор сам мог их менять), например /sale.

          HTTP-запрос перехватывался с помощью middleware, посылался запрос в БД и если там был нужный request.uri, то он подменялся и рельсы рендерили нужную страничку.
            +1
            Сами рельсы реализованы поверх rack (не изначально). Также куча плагинов работают как rack middleware, например omniauth.
            +2
            my_rack_proc = lambda { | ENV | [200, {}, [«Hello. The time is #{Time.now}»]] }


            SyntaxError: (irb):6: formal argument cannot be a constant
            my_rack_proc = lambda {|ENV| [200, {}, [«Hello. The time…

            переведите ENV в нижний регистр
              +1
              Благодарю. Гугл транслейт пошалил, а я и не заметил.
              +7
              >> Rack:: Handler.constants

              => [: CGI,: FastCGI,: Mongrel,: EventedMongrel,: SwiftipliedMongrel,: WEBrick,: LSWS,: SCGI,: Тонкие]


              А в вакансиях пишут «Нужное знание технологий: Java, Hibernate, Весна» :)

              Исправте на:
              [:CGI, :FastCGI, :Mongrel, :EventedMongrel, :SwiftipliedMongrel, :WEBrick, :LSWS, :SCGI, :Thin]

              И уберите пробел между Handler
                +1
                Если мне память не изменяет, то lambda и proc немного разные вещи.
                  +1
                  Разные. И различия в них достойны целой статьи (потому что там хватает своих тонкостей).
                    +1
                    Это я к
                    >>В руби блоки — это не объекты, но они могут быть преобразованы в объекты класса Proc. Это можно сделать, вызвав метод lambda класса Object.
                      +2
                      С помощью метода lambda тоже создается объект класса Proc.

                      image
                  0
                  Я слышал, что в Руби анонимные функции должны занимать не более одной строки. Правда ли это?
                    +6
                    Это в питоне так насколько я знаю, а в руби нет
                    0
                    Статья полна довольно странными утверждениями и приёмами.

                    HTTP — это простой протокол.
                    Очень спорно.

                    # irb --simple-prompt
                    >> require 'rack'
                    => true


                    gem list rack

                    метод lambda класса ObjectВ Object он приватен. А определён он в Kernel.

                    require './my_app'

                    А в целом да, полезно знать.
                      0
                      Поправки.

                      > HTTP — это простой протокол.
                      Очень спорно.

                      > # irb --simple-prompt
                      > >> require 'rack'
                      > => true

                      gem list rack

                      > метод lambda класса Object

                      В Object он приватен. А определён он в Kernel.

                      > require './my_app'

                      А в целом да, полезно знать.

                    Only users with full accounts can post comments. Log in, please.