- Спецификация Rack
- Rack приложения
- Rack Middleware
- Основные компоненты и структура фреймворка
Для создания универсального интерфейса между веб серверами/серверами приложений и Ruby фреймворками была создана спецификация Rack. В общем смысле она включает 3 положения:
-
Для того чтобы соответствовать спецификации Rack, приложение Ruby должно представлять собой любой объект, отвечающий на метод
call
. Таким объектом может быть экземпляр класса, класс, модуль или дажеProc
объект. -
Метод
call
должен принимать единственный аргументenv
(как правило его именуют именно так), в котором содержится хэш с информацией о запросе (включая HTTP заголовки). -
Метод
call
должен возвращать массив из трёх элементов:
- Статус ответа
- Хэш заголовков
- Объект тела ответа, отвечающий на метод
each
(в самом простом случае это может быть массив)
Пример простейшего Rack приложения:
# rackapp.rb
require 'rack'
app = Proc.new { |env| [200, {}, ['Hey, this is Rack, bro!']] }
Rack::Handler::WEBrick.run app
В определённые моменты времени возникают ситуации когда нам требуется выполнить какую-то работу в нашем веб-приложении, и при этом эти задачи не относятся напрямую к логике приложения.
Например, при обработке каждого HTTP запроса нам хочется отправлять какие-то данные на сервер статистики или аналитики. Или упрвлять HTTP заголовками кэширования. Или, возможно, модифицировать HTTP ответ в отдельных случаях.
Конечно мы можем всё это написать и определить в рамках приложения. И всё же, это будет логика которую хорошо бы определить где-то ещё, чтобы при разработке приложения лишний раз не отвлекаться на вещи общего характера.
Именно в таких случаях и используется Middleware. В общем смысле, это может быть любое программное обеспечение. Иными словами, это может быть программа, написанная на любом языке которая стоит между веб-сервером и вашим приложением. В общем, это лист салата в бутерброде: вроде бы его почти незаметно, но чуть-чуть на общий вкус он оказывает виляние.
Рассматривая Middleware в Ruby приложениях, прежде всего стоит сказать что это обыкновенный Ruby класс. Однако здесь есть некоторые особенности которые прямо сейчас мы рассмотрим на примере.
Если вы напишете самое простое Rack приложение которое возвращает строку и попробуете сделать несколько запросов из консоли (например с помощью программы curl
), то вы заметите что курсор при выводе результата не переходит на новую строку. Это в принципе понятно, потому что в ответе нашего приложения нет символа новой строки, поэтому новая строка и не печатается (и курсор на новую строку не переходит). Вот как сейчас выглядит наше приложение:
# config.ru
require_relative 'app'
run App
# app.rb
class App
def self.call(env)
[200, {}, ['What about new lines?']]
end
end
Теперь создадим Middleware (промежуточный слой), который будет к телу всех HTTP ответов добавлять символ новой строки:
# middleware/newline
class Newline
def initialize app
@app = app
end
def call env
status, headers, body = @app.call env
body << "\n"
[status, headers, body]
end
end
Обратите внимание на структуру Middleware класса: он содержит 2 метода: конструктор initialize
и метод call
. Что касается конструктора - он принимает наше Rack приложение которое мы передавали методу run
в файле config.ru
(загляните прямо сейчас в этот файл чтобы лучше понимать о чём речь).
Метод call
реализует спецификацию Rack, то есть он принимает один аргумент с окружением запроса env
и возвращает массив из 3-х элементов: статус, хэш заголовков и тело ответа (любой объект отвечающий на метод each
).
Теперь, если в содержании метода call
вы оставите только последний массив и будете что-то в нём возвращать, то всё будет работать, но дело в том что наше Rack приложение при этом вообще не будет вызываться. Чтобы исправить это мы должны вызвать Rack приложение самостоятельно и получить его ответ. Именно это и происходит в методе call
: status, headers, body = @app.call env
. Мы используем параллельное присваивание, хотя можно было бы написать и так:
def call env
app_result = @app.call env
status = app_result[0]
headers = app_result[1]
body = app_result[2]
body << "\n"
[status, headers, body]
end
Далее мы добавляем к телу символ новой строки и возвращаем массив с результатом. Всё что осталось теперь сделать - подключить наш Middleware класс в приложение:
require_relative 'app'
require_relative 'middleware/newline'
use Newline
run App
Исследуйте исходный код Gem-а Rack и найдите методы:
Для класса Rack::Request
:
-
Метод возвращающий название агента пользователя
-
Метод, возвращающий IP адрес пользователя
-
Метод, возвращающий полный URL запроса (включая строку запроса)
Для класса Rack::Response
:
-
Метод добавления данных в тело ответа
-
Метод завершающий формирование объекта ответа
-
Метод, позволяющий совершить редирект на указанный URL
Исследуйте исходный код Gem-а Rack и найдите классы или модули Middleware для:
-
Установки заголовка
Content-Type
для HTTP ответа на запрос -
Установки заголовка ответа
ETag
Запустите Rack приложение лобстер, исходный код которого находится внутри Gem-а Rack.
Превратите приложение DiceGame
(исходный код в архиве с кодом к заданию) в Rack приложение. Вы можете сделать это на свой вкус, основная идея в том, чтобы управлять ходом игры можно было из клиента, например веб браузера. Следующие положения могут помочь сориентироваться:
-
Используйте различные URL для совершения различных действий. Например,
/turn
- для совершения хода,/finish
- для завершения игры -
Используйте класс
Rack::Request
для создания объекта запроса, а затем – методparams
для извлечения параметров запроса -
Воспользуйтесь шаблонизатором
ERB
(или любым другим, например, Slim) для формирования HTML страниц с ответом на HTTP запрос
- GeekBrains
- Электронная почта:
[email protected]
- Slack канал