Что такое промежуточное ПО Rack в Ruby? Я не мог найти никакого хорошего объяснения тому, что они подразумевают под "промежуточным ПО".
Средство промежуточного уровня стойки - это больше, чем "способ фильтрации запроса и ответа" - это реализация шаблона конструирования конвейера Rack.
Он очень четко отделяет различные этапы обработки запроса - разделение проблем является ключевой целью всех хорошо разработанных программных продуктов.
Например, с помощью стойки я могу выполнять отдельные этапы работы трубопровода:
Аутентификация: при поступлении запроса правильно ли указаны данные входа в систему? Как проверить эту OAuth, базовую аутентификацию HTTP, имя/пароль?
Авторизация: "является ли пользователь уполномочен выполнять эту конкретную задачу?", то есть на основе ролей безопасности.
Кэширование: я уже обработал этот запрос, могу ли я вернуть кешированный результат?
Украшение: как я могу улучшить запрос, чтобы лучше обрабатывать последующие процессы?
Мониторинг производительности и использования: какую статистику я могу получить от запроса и ответа?
Выполнение: действительно обрабатывать запрос и предоставлять ответ.
Возможность разделить различные этапы (и необязательно включать их) - отличная помощь в разработке хорошо структурированных приложений.
Там также есть отличная экосистема, развивающаяся вокруг Rack Middleware - вы должны иметь возможность находить готовые компоненты стойки, чтобы выполнить все шаги выше и более. См. Rack GitHub wiki для списка промежуточного программного обеспечения.
Middleware - это ужасный термин, который относится к любому программному компоненту/библиотеке, который помогает, но не участвует непосредственно в выполнении какой-либо задачи. Очень распространенными примерами являются протоколирование, аутентификация и другие общие, горизонтальные компоненты обработки. Это, как правило, все, что нужно каждому из множества приложений, но не слишком много людей заинтересованы (или должны быть) в создании самих себя.
Комментарий о том, что это способ фильтрации запросов, возможно, исходит из экрана RailsCast episode 151: Rack Middleware.
Средство промежуточного уровня для стойки вышло из стойки, и есть отличное введение в Введение в промежуточное ПО промежуточного уровня.
Здесь есть вступление к промежуточному программному обеспечению в Wikipedia здесь.
Прежде всего, Rack - это ровно две вещи:
Стойка - интерфейс веб-сервера
Сами основы стойки - это простое соглашение. Каждый веб-сервер, совместимый с стойкой, всегда будет вызывать метод вызова на объекте, который вы ему даете, и обслуживать результат этого метода. Rack точно определяет, как должен выглядеть этот метод вызова, и что он должен вернуть. Эта стойка.
Давайте попробуем простую попытку. Я буду использовать WEBrick в качестве стойкого веб-сервера, но любой из них будет делать. Позвольте создать простое веб-приложение, которое возвращает строку JSON. Для этого мы создадим файл config.ru. Config.ru будет автоматически вызываться консольной консолью, которая будет просто запускать содержимое config.ru в стойке, совместимом с веб-сервером. Поэтому добавьте следующее в файл config.ru:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
Как указано в соглашении, наш сервер имеет метод, называемый вызовом, который принимает хеш среды и возвращает массив с формой [status, headers, body] для обслуживающего веб-сервера. Попробуйте это, просто позвонив в rawn. Сервер, совместимый с стойкой по умолчанию, может быть, WEBrick или Mongrel запустится и сразу же ждет запросов на обслуживание.
$ rackup
[2012-02-19 22:39:26] INFO WEBrick 1.3.1
[2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0]
[2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292
Позвольте протестировать наш новый JSON-сервер, либо свернув или посетив url http://localhost:9292/hello.json
и voila:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Это работает. Большой! Это основа для каждой веб-структуры, будь то Rails или Sinatra. В какой-то момент они реализуют метод вызова, работают через весь код структуры и, наконец, возвращают ответ в типичной форме [статус, заголовки, тело].
В Ruby on Rails, например, запросы на стойку попадают в класс ActionDispatch::Routing.Mapper
, который выглядит следующим образом:
module ActionDispatch
module Routing
class Mapper
...
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
end
def matches?(env)
req = @request.new(env)
...
return true
end
def call(env)
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
...
end
end
Таким образом, в основном Rails проверяет, зависит от хэша env, если какой-либо маршрут совпадает. Если это так, то передается env хэш на приложение для вычисления ответа, в противном случае он сразу отвечает 404. Таким образом, любой веб-сервер, который соответствует стандарту интерфейса стойки, может обслуживать полностью взорванное приложение Rails.
Middleware
Стойка также поддерживает создание промежуточных слоев. Они в основном перехватывают запрос, делают с ним что-то и передают его. Это очень полезно для универсальных задач.
Скажем, мы хотим добавить журнал на наш JSON-сервер, который также измеряет, сколько времени займет запрос. Мы можем просто создать регистратор промежуточного программного обеспечения, который выполняет именно это:
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
Когда он создается, он сохраняет копию фактического приложения стойки. В нашем случае это экземпляр нашего JSONServer. Rack автоматически вызывает метод вызова на промежуточном программном обеспечении и ожидает обратно массив [status, headers, body]
, как и возвращает наш JSONServer.
Итак, в этом промежуточном программном обеспечении принимается начальная точка, тогда фактический вызов JSONServer производится с помощью @app.call(env)
, тогда регистратор выводит запись журнала и, наконец, возвращает ответ как [@status, @headers, @body]
.
Чтобы сделать наш маленький rackup.ru использовать это промежуточное программное обеспечение, добавьте к нему RackLogger следующим образом:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
use RackLogger
map '/hello.json' do
run JSONServer.new
end
Перезагрузите сервер и voila, он выводит журнал на каждый запрос. Стойка позволяет добавлять несколько посредников, вызываемых в том порядке, в котором они добавлены. Это просто отличный способ добавить функциональность, не меняя ядро приложения стойки.
Стойка - драгоценный камень
Хотя стойка - в первую очередь - это конвенция, она также является камнем, который обеспечивает отличную функциональность. Один из них мы уже использовали для нашего сервера JSON, команды rawn. Но там еще! Драгоценный камень в стойке обеспечивает небольшие приложения для множества случаев использования, например, для статических файлов или целых каталогов. Посмотрим, как мы обслуживаем простой файл, например, очень простой HTML файл, расположенный в htmls/index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Возможно, мы захотим выполнить этот файл с корневого сайта, поэтому добавьте следующее в наш config.ru:
map '/' do
run Rack::File.new "htmls/index.html"
end
Если мы посетим http://localhost:9292
, мы увидим, что наш html файл отлично отображается. Это было легко, правда?
Позвольте добавить весь каталог javascript файлов, создав некоторые файлы javascript под /javascripts и добавив следующее в config.ru:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Перезагрузите сервер и зайдите в http://localhost:9292/javascript
, и вы увидите список всех файлов javascript, которые вы можете теперь включить прямо из любого места.
У меня возникла проблема с пониманием того, что я считаю себя очень хорошим временем. Я только полностью понял это после работы над этим миниатюрным веб-сервером . Я поделился своими знаниями о Rack (в форме истории) здесь, в моем блоге: http://gauravchande.com/what-is-rack-in-ruby-rails
Обратная связь более чем приветствуется.
Средство промежуточного промежуточного уровня - это способ фильтрации запросов и ответов, поступающих в ваше приложение. Компонент промежуточного программного обеспечения находится между клиентом и сервером, обрабатывает входящие запросы и исходящие ответы, но это больше, чем интерфейс, который можно использовать для общения с веб-сервером. Его используют для группировки и упорядочения модулей, которые обычно являются классами Ruby, и определяют зависимость между ними. Модуль промежуточного программного обеспечения стойки должен: - иметь конструктор, который принимает следующее приложение в стеке в качестве параметра - отвечать на метод "вызов", который принимает хеш среды как параметр. Возвращаемое значение этого вызова представляет собой массив: код состояния, хеш среды и тело ответа.
config.ru
пример минимального запуска
app = Proc.new do |env|
[
200,
{
'Content-Type' => 'text/plain'
},
["main\n"]
]
end
class Middleware
def initialize(app)
@app = app
end
def call(env)
@status, @headers, @body = @app.call(env)
[@status, @headers, @body << "Middleware\n"]
end
end
use(Middleware)
run(app)
Запустите rackup
и зайдите в localhost:9292
. Выход:
main
Middleware
Итак, понятно, что Middleware
обертывает и вызывает основное приложение. Поэтому он может предварительно обработать запрос и каким-либо образом обработать ответ.
Как объяснялось в: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack, Rails использует средние среды Rack для многих своих функций, и вы можете добавить себя тоже с config.middleware.use
семейные методы.
Преимущество внедрения функциональности в промежуточном программном обеспечении заключается в том, что вы можете повторно использовать его в любой среде Rack, поэтому все основные Ruby, а не только Rails.
Я использовал промежуточное ПО Rack для решения нескольких проблем:
В обоих случаях он предоставлял довольно элегантные исправления.
Rack обеспечивает минимальный интерфейс между веб-серверами, поддерживающими структуры Ruby и Ruby.
С помощью Rack вы можете написать приложение для стойки.
Стойка передаст хэш среды (хэш, содержащийся внутри HTTP-запроса от клиента, состоящий из заголовков, подобных CGI) для вашего приложения Rack, которое может использовать вещи, содержащиеся в этом хэше, чтобы делать все, что захочет.
Чтобы использовать Rack, вы должны предоставить "приложение" - объект, который отвечает методу #call
с помощью параметра "Хэш среды" как параметр (обычно определяемый как env
). #call
должен возвращать массив из трех значений:
each
).Вы можете написать приложение Rack, которое возвращает такой массив - это будет отправлено обратно вашему клиенту, Rack, внутри Response (это будет фактически экземпляр класса Rack::Response
[щелкните, чтобы перейти к документам]).
gem install rack
config.ru
- стойка знает, что нужно искать.Мы создадим крошечное приложение Rack, которое возвращает Response (экземпляр Rack::Response
), который Response Body - это массив, содержащий строку String: "Hello, World!"
.
Мы запустим локальный сервер, используя команду rackup
.
При посещении соответствующего порта в нашем браузере мы увидим "Привет, мир!". отображается в окне просмотра.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Запустите локальный сервер с помощью rackup
и посетите localhost:9292, и вы увидите "Hello, World!". оказаны.
Это не исчерпывающее объяснение, но, по сути, здесь происходит то, что клиент (браузер) отправляет HTTP-запрос на стойку через ваш локальный сервер, а Rack создает экземпляр MessageApp
и запускает call
, передавая Окружающая среда Хэш как параметр в методе (аргумент env
).
Rack принимает возвращаемое значение (массив) и использует его для создания экземпляра Rack::Response
и отправляет его обратно клиенту. Браузер использует magic для печати 'Hello, World!' на экран.
Кстати, если вы хотите посмотреть, как выглядит хэш среды, просто поставьте puts env
под def call(env)
.
Как бы то ни было, то, что вы написали здесь, - это приложение Rack!
В нашем маленьком приложении Rack мы можем взаимодействовать с хешем env
(см. здесь для получения дополнительной информации об хеш-среде среды).
Мы реализуем возможность для пользователя вводить свою собственную строку запроса в URL-адрес, поэтому эта строка будет присутствовать в HTTP-запросе, инкапсулированная как значение в одну из пар ключ/значение хеша среды.
Приложение "Наша стойка" получит доступ к этой строке запроса из хэша среды и отправит обратно клиенту (наш браузер в этом случае) через тело в ответ.
Из документов стойки в среде Hash: "QUERY_STRING: часть URL-адреса запроса, которая следует за?, если таковая имеется. Может быть пустым, но всегда требуется!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Теперь rackup
и зайдите в localhost:9292?hello
(?hello
, являющуюся строкой запроса), и вы увидите "hello", отображаемый в окне просмотра.
Мы будем:
MessageSetter
,env
,MessageSetter
будет вставлять ключ 'MESSAGE'
в хэш env, его значение 'Hello, World!'
, если env['QUERY_STRING']
пусто; env['QUERY_STRING']
если нет,@app.call(env)
- @app
следующее приложение в "Stack": MessageApp
.Во-первых, "длинная рука":
#./middleware/message_setter.rb
class MessageSetter
def initialize(app)
@app = app
end
def call(env)
if env['QUERY_STRING'].empty?
env['MESSAGE'] = 'Hello, World!'
else
env['MESSAGE'] = env['QUERY_STRING']
end
@app.call(env)
end
end
#./message_app.rb (same as before)
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
#config.ru
require_relative './message_app'
require_relative './middleware/message_setter'
app = Rack::Builder.new do
use MessageSetter
run MessageApp.new
end
run app
Из Rack:: Builder docs мы видим, что Rack::Builder
реализует небольшую DSL для итеративного построения приложений Rack. Это в основном означает, что вы можете создать "Stack", состоящий из одного или нескольких Middlewares и приложения "нижнего уровня" для отправки. Все запросы, поступающие на ваше приложение нижнего уровня, будут сначала обработаны вашим промежуточным программным обеспечением.
#use
указывает промежуточное программное обеспечение для использования в стеке. В качестве аргумента требуется промежуточное ПО.
Средство промежуточного уровня должно:
call
, который принимает хеш среды как параметр. В нашем случае "промежуточное ПО" MessageSetter
, конструктор - это метод MessageSetter initialize
, "следующее приложение" в стеке - MessageApp
.
Итак, из-за того, что Rack::Builder
делает под капотом, аргумент app
метода MessageSetter
initialize
имеет значение MessageApp
.
(перед тем, как продолжить движение),
Поэтому каждый кусочек Middleware по существу "пропускает" существующий хэш среды к следующему приложению в цепочке - так что у вас есть возможность мутировать этот хэш среды в промежуточном программном обеспечении, прежде чем передавать его следующему приложению в стеке.
#run
принимает аргумент, который является объектом, который отвечает на #call
и возвращает ответ Rack Response (экземпляр Rack::Response
).
Используя Rack::Builder
, вы можете создавать цепочки Middlewares, и любой запрос к вашему приложению будет обрабатываться каждым промежуточным программным обеспечением в свою очередь, прежде чем, наконец, будет обработан финальной частью в стеке (в нашем случае MessageApp
). Это чрезвычайно полезно, поскольку он отделяет различные этапы обработки запросов. Что касается "разделения проблем", это не может быть намного чище!
Вы можете построить "конвейер запросов", состоящий из нескольких Middlewares, которые имеют дело с такими вещами, как:
(над пунктами пули от другого ответа на эту тему)
Вы часто увидите это в профессиональных приложениях Sinatra. Sinatra использует стойку! См. здесь для определения того, что Sinatra IS!
В качестве заключительного примечания наш config.ru
может быть написан в коротком стиле, создавая точно такую же функциональность (и это то, что вы обычно увидите):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
И чтобы более четко показать, что делает MessageApp
, вот его "длинная рука", которая явно показывает, что #call
создает новый экземпляр Rack::Response
с необходимыми тремя аргументами.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end