Статьи

Стойка для Middlewares

В предыдущей статье мы тонко замаскировали учебник по Rack как сравнение PHP и Ruby. Целью статьи было указать, что большинство разработчиков PHP начинают довольно хорошо продуманный учебный процесс. Мы направляемся в «здравствуй мир», подхватывая какой-то базовый HTML, чередующийся с PHP, переходя к разделению представлений и бизнес-логики, а затем используем подходящую среду, управляемую сообществом. Тем не менее, когда дело доходит до изучения Ruby, мы забываем обо всем этом, берем Rails и начинаем обучение.

В нем много говорится о Rails (и / или Ruby), так что многие разработчики постоянно выбирают язык. Многие великие разработчики потратили часы на то, чтобы абстрагироваться от рутинной работы в Rails, и, как ни удивительно, нам иногда нужно запачкать руки, прежде чем мы сможем укрепить наше понимание.

Я подчеркнул, что сам по себе Ruby может быть болезненным при разработке для Интернета. Следствием этого является обход хорошей кривой обучения, которую мы имеем с PHP. В отличие от PHP, мы не можем просто связать HTML с ERB, разместить его на веб-сервере и ожидать, что он будет работать. Тем не менее, мы можем использовать Rack и достичь некоторой непосредственности, которую мы получаем с помощью PHP-скриптов, в то же время абстрагируя достаточно HTTP-протокола, чтобы сохранить нас в здравом уме.

Больше чем монгрелы и единороги

Предыдущая статья завершилась упоминанием мистической фразы «middleware». Итак, что же такое промежуточное ПО? Существует большое объяснение переполнения стека , но в этом контексте мы собираемся использовать 50% правильное объяснение. Это кусок кода, который находится между сервером и приложением, позволяя нам фильтровать определенные ответы и запросы.

Чтобы дать вам немного больше понимания, Rails, Sinatra Merb и т. Д. Все построены на Rack. Rack, за исключением формы, дающей нам общий интерфейс для нашего сервера (Thin, Mongrel, Unicorns и т. Д.), Используется для создания стека, подобного структуре компонентов, которая формирует конечное приложение. Мы можем внедрить приложения mini Rack в этот стек для выполнения желаемых задач.

Получить суть?

Мы собираемся использовать эти знания для создания нашего первого промежуточного программного обеспечения Rack. Целью промежуточного программного обеспечения является сбор ответов от нашего приложения, содержащих URL-адреса GitHub Gist , и автоматическое преобразование обычного текста URL в довольно отформатированный встроенный гист; Красноречиво описано в этом посте GitHub .

Для начала мы создадим скелетное приложение Rack. Как вы, несомненно, помните, нам нужен метод call который принимает хэш среды, и кажется разумным захватывать любое приложение, использующее наше промежуточное ПО.

 module Rack class Gist def initialize(app) @app = app end def call(env) status, headers, response = @app.call(env) [status, headers, response] end end end 

Как вы можете видеть, я поместил в пространство имен приложение Gist с помощью модуля Rack, это позволяет нам вызывать его через Rack::Gist и, конечно, завершает имя этого приложения. Так что переименуйте родительский каталог из super_awesome_ninja_rack_middleware в rack_gist сейчас!

Как и все приложения Rack, наше приложение реагирует на метод call . На данный момент этот метод просто фиксирует статус, заголовки и тело. Поскольку мы имеем дело с ответом в этой заявке, body было точно названо для response для ясности.

Мы хотим только анализировать HTML-ответы, поэтому давайте удостоверимся в этом, изучив заголовки.

 module Rack class Gist def initialize(app) @app = app end def call(env) status, @headers, response = @app.call(env) if html? #do something end [status, @headers, response] end private def html? @headers["Content-Type"].include? "text/html" end end end в module Rack class Gist def initialize(app) @app = app end def call(env) status, @headers, response = @app.call(env) if html? #do something end [status, @headers, response] end private def html? @headers["Content-Type"].include? "text/html" end end end 

Теперь мы хотим проверить ответ на любые суть. Поскольку это довольно простая задача, мы будем использовать регулярное выражение.

 module Rack class Gist def initialize(app) @app = app end def call(env) status, @headers, response = @app.call(env) if html? parsed_response = "" response.each do |r| parsed_response = r.gsub(/(https://|)gist.github.com/(d+)/) do |gist| gist = "https://" + gist unless gist.start_with? "https://" "<script src="#{gist}.js"></script>" end end response = [parsed_response] end [status, @headers, response] end private def html? @headers["Content-Type"].include? "text/html" end end end в module Rack class Gist def initialize(app) @app = app end def call(env) status, @headers, response = @app.call(env) if html? parsed_response = "" response.each do |r| parsed_response = r.gsub(/(https://|)gist.github.com/(d+)/) do |gist| gist = "https://" + gist unless gist.start_with? "https://" "<script src="#{gist}.js"></script>" end end response = [parsed_response] end [status, @headers, response] end private def html? @headers["Content-Type"].include? "text/html" end end end 

Мы просто ищем gist.github.com адрес gist.github.com , добавляя протокол https:// мере необходимости, затем gist.github.com URL-адрес gist в соответствующие теги сценария перед повторным назначением ответа.

Выполнено. Ну, не совсем.

Куда все пошло не так?

Таким образом, мы продвинулись довольно далеко, не зная много о том, какое приложение будет использовать это промежуточное ПО, протокол HTTP, и, конечно, мы не написали ни одного теста.

Я не говорю о модульных тестах. Одни модульные тесты могут не уловить ошибку, которую я имею в виду, если мы мало знаем о HTTP или спецификации Rack.

Давайте подумаем о поведении нашего промежуточного программного обеспечения: оно захватывает ответ, возможно модифицирует его и передает обратно в нормальном потоке. Глядя на спецификацию Rack , мы видим, что заголовок должен иметь длину содержимого. Это соответствует спецификации HTTP для заголовка, который должен содержать правильную длину содержимого. Очевидно, что если промежуточное ПО изменяет ответ, длина контента теперь будет неправильной.

Rack поставляется с модулем Rack::Lint , что позволяет нам проверять, соответствует ли наше приложение данному контракту в процессе разработки. Конечно, если мы добавим тесты в промежуточное ПО, мы получим следующую ошибку.

 Rack::Lint::LintError: Content-Length header was 81, but should be 108 

Это дешевое решение. Rack прекрасно подходит для того, чтобы предоставлять мало, но достаточно всего, что нам нужно. На этот раз это Rack::Utils . Мы можем включить модуль Utils а затем вызвать bytesize и установить заголовок на новую длину.

 include Rack::Utils # # ... # @headers['Content-Length'] &&= bytesize(parsed_response).to_s 

Метод bytesize ничего особенного. Проверьте источник, и вы обнаружите, что он просто возвращает размер строки, переданной ему, буквально string.size . Но мне нравится хранить все вещи в Rack, а bytesize специфичен для нашего домена, поэтому давайте не будем просто использовать response.size .

Еще один полезный инструмент в поясе летучей мыши («Holy Rack, Batman!») — драгоценный камень Rack Test . Эта компактная маленькая жемчужина предоставляет помощники для таких вещей, как установка заголовков запросов для наших тестов, отслеживание перенаправлений и управление сессиями. Я считаю, что это помогает мне сосредоточиться больше на тесте, чем на настройке.

В дикой природе

У нас есть все промежуточное ПО, готовое к развертыванию. Как мы это сделаем, точно? С Rails, начиная с v3, промежуточное ПО Rack стало частью ядра, сделав внедрение промежуточного ПО очень простым. Если мы хотим применить его к ответу одного контроллера, вставьте источник в каталог нашего vendor или lib .

 class GistController < ApplicationController use Rack::Gist def index # views with embedded gists end end 

Вы должны быть знакомы с методом use здесь, это обычный старый Rack в действии.

Конечно, если наше промежуточное ПО применимо ко всему приложению, в config/application.rb в разделе config мы можем добавить.

 module GistTest class Application < Rails::Application # other config config.middleware.use Rack::Gist end end 

Rack::Gist вводится в наш промежуточный стек при каждом запросе. Вуаля.

Еще одна вещь, которую мне нравится делать, — это упаковывать промежуточное программное обеспечение как драгоценные камни (ну, они должны быть повторно использованы). Источник плюс тесты можно найти на обычном подозреваемом .

Куда дальше?

До сих пор мы говорили о Rack и о том, как он формирует основу для основных сред Ruby, которые мы знаем и любим. Это больше, чем просто интерфейс сервера для наших приложений. Мы можем использовать его возможности для добавления низкоуровневой функциональности гораздо более простым и портативным способом, чем любой помощник.

Я хотел показать промежуточное программное обеспечение, которое фактически дало бы нам что-то кроме статистики запроса. Мы могли бы пойти дальше и справиться со случаями, когда сущность находится в якорном теге, используя драгоценные камни, такие как nokogiri или hpricot . Я предполагаю, что есть даже случай, чтобы просто сделать все это в JavaScript.

Независимо от того, что мы достигли, это хлеб с маслом для большинства полезных промежуточных программ Rack. Отличный, вдохновляющий ресурс для вашего следующего приложения Rack — CodeRack . Выберите несколько проектов и изучите источник.

Надеемся, что в будущем, прежде чем вы добавите сложность к своему приложению для определенных ответов ( вспоминается ReCaptcha ), подумайте, как можно решить проблему в промежуточном программном обеспечении, в большинстве случаев это просто имеет смысл.