- Приложение Rack
- Middlewares
- Тестирование Rack-приложений
- Мидлвары Rack
- Хелперы Rack
- Sinatra
- Заключение
Rack – это интерфейс для взаимодействия веб-сервера с HTTP-запросами. У него есть несколько функций:
Стандарт интерфейса веб-сервера. Rack определяет, как серверы обрабатывают запросы и взаимодействуют друг с другом. Позволяет использовать различные серверы для запуска приложений
Каркас для Middlewares. Rack работает как каркас для middleware, которые обрабатывают запросы по конвейерному принципу.
Библиотека. Rack содержит вспомогательные функции для более быстрой разработки. Используется в таких фреймворках, как Sinatra и Rails.
Понимание Rack важно для разработки на Sinatra и Rails, поскольку они подчиняются его стандартам.
Приложение Rack
Чтобы запустить Rack, необходимо импортировать библиотеку, вызвать хендлер и применить метод run()
, передавая объект, который содержит метод call()
. Приложение запускается командой ruby app.rb
# app.rb
require 'rackup'
class MyApp
def call(_env)
[200, {'Content-Type' => 'text/html'}, ["Hello"]]
end
end
Rackup::Handler::WEBrick.run MyApp.new, :Port => 3000
Метод call()
должен вернуть массив из трех элементов:
- Статус ответа.
- Хедеры в виде хэша.
- Тело ответа. Тело ответа обычно представляет собой массив строк
Разработчики Rack также создали консольную утилиту под названием rackup. Эта утилита ищет файл с именем config.ru в текущей папке и запускает сервер на порту 9292. Если в config.ru указано приложение, то при отправке GET-запроса на порт 9292 сервер вернет тело ответа, указанное в качестве третьего элемента в конфигурации приложения.
# config.ru
class MyApp
def call(_env)
[200, {'Content-Type' => 'text/html'}, ["Hello"]]
end
end
run MyApp.new
Запуск может выполняться одной из команд в зависимости от используемого сервера
# автоматически ищется файл config.ru
rackup
rackup -s thin
thin start
puma
unicorn
passenger start
Проверяем работу приложения
# Запустили сервер
rackup
# В другом терминале выполнили запрос
curl -X GET localhost:9292
# => Hello!
Middlewares
Middleware – это фильтры запросов, которые обрабатывают информацию о запросе и передают её следующему компоненту.
Применение middleware:
Авторизация
Middleware может управлять доступом, включая встроенные решения для basic-auth
Мониторинг
Можно отслеживать количество запросов и их время выполнения
Логирование
Подходит для записи работы приложения, особенно на уровне системы.
Сериализация
Поддерживает передачу данных, включая динамические переменные.
Роутинг
Доступ к параметрам запроса позволяет определять, как следует обрабатывать запрос.
Бизнес-логика
Может реализовываться через вызов сервисных объектов в middleware.
Запрос поступает в приложение и передается через цепочку middleware, каждая из которых обрабатывает определенную логику. Ответ формируется последней middleware.
Каждый компонент middleware должен возвращать три элемента: статус, хедеры и тело ответа.
Ниже пример приложения, содержащее миддлвар
# config.ru
class MyMiddleware
def initialize(app1)
@app1 = app1
end
def call(env)
puts 'middleware_before'
# env содержит запрос
status, headers, body = @app1.call(env)
puts 'middleware_after'
request = Rack::Request.new(env)
if request.path == '/'
case request.request_method
when 'GET'
[status, headers, body]
when 'POST'
[201, headers.merge({'x-created' => 'True'}), ['Item was successfully created']]
end
else
[404, {}, ["Not Found"]]
end
end
end
class App
def call(env)
puts 'app_run'
[200, {}, ["success"]]
end
end
# Добавляется миддлвар
use MyMiddleware
# Запуск приложения
run App.new
Пример приложения, которое обрабатывает POST запрос:
require 'json'
class MyMiddleware
def initialize(app1)
@app1 = app1
end
def call(env)
status, headers, body = @app1.call(env)
request = Rack::Request.new(env)
body = {
path: request.path,
verb: request.request_method,
ip: request.ip,
cookies: request.cookies,
params: request.params,
body: JSON.parse(request.body.read)
}
[200, {}, [body.to_json]]
end
end
class App
def call(env)
end
end
use MyMiddleware
run App.new
Запуск и выполнение запроса:
rackup
curl -x POST localhost:9292/users?sort=desc -d "{\"login\":\"admin\",\"password\":\"password\"}"
Пример Middleware, который обрабатывает предыдущий ответ от приложения и добавляет к телу новую информацию. В этом примере добавляется к телу ответа добавляется текущее время
class TimeStamp
def initialize(app)
@app = app
end
def call(env)
# Вызываем приложение и получаем его ответ
prev_response = @app.call(env)
status, headers, prev_body = prev_response
# Если статус не 200, возвращаем предыдущий ответ без изменений,
# чтобы не обрабатывать не успешные ответы
return prev_response if status != 200
# Получаем текущее время в формате строки
current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
# Добавляем текущее время к предыдущему телу ответа
next_body = prev_body.push('</br>', "Текущее время: #{current_time}")
# Возвращаем новый ответ с добавленным временем
[status, headers, next_body]
end
end
В этом примере миддлвара Timestamp
принимает на вход приложение Rack и перехватывает его ответ. Если статус ответа равен 200 (успешный ответ), миддлвара добавляет к телу ответа текущую дату и время в виде строки.
Приложение с базовой авторизацией
use Rack::Auth::Basic do |username, password|
username == 'admin' && password == 'password'
end
class App
def call(env)
puts env["HTTP_AUTHORIZATION"]
[200, {'Content-Type' => 'text/html'}, ["You have been successfully logged in."]]
end
end
run App.new
Запуск и запрос с авторизацией
rackup
curl -u admin:password -i http://localhost:9292
Тестирование Rack-приложений
Запуск выполняется командой ruby test.rb
# test.rb
require 'minitest/autorun'
require 'rack/test'
class MyApp
def call(env)
[200, {'X-success' => true}, ["Success response"]]
end
end
describe "MyApp" do
include Rack::Test::Methods
def app
MyApp.new
end
it 'check response status' do
get '/'
assert last_response.ok?
end
it 'check response headers' do
get '/'
assert_equal last_response.headers, {'X-success' => true}
end
it 'check response body' do
get '/'
assert_equal last_response.body, "Success response"
end
end
#
# https://www.rubydoc.info/github/brynary/rack-test/Rack/Test/Methods
# https://devhints.io/rack-test
Мидлвары Rack
Rack предоставляет различные готовые middleware для улучшения функционала, вот некоторые из них:
Сам Rack поставляется со следующим промежуточным программным обеспечением:
Rack::Files
для раздачи статических файлов.Rack::Events
для создания удобных хуков при получении запроса и отправке ответа.Rack::Head
для возврата пустого тела для HEAD-запросов.Rack::Lock
для сериализации запросов с помощью мьютекса.Rack::Reloader
для перезагрузки приложения, если были изменены файлы.Rack::Runtime
для включения в заголовок ответа времени, затраченного на обработку запроса.Rack::ShowException
для перехвата необработанных исключений и представления их в удобном виде.Rack::MethodOverride
для изменения метода запроса на основе переданного параметра.
Хелперы Rack
Rack предоставляет множество хелперов:
Rack::Request
- обеспечивает разбор строки запроса и работу с несколькими частями.Rack::Response
- для удобной генерации HTTP-ответов и обработки cookie.Rack::MockRequest
иRack::MockResponse
для эффективного и быстрого тестирования Rack-приложений без реальных HTTP-сессий.Rack::Directory
- для раздачи файлов в директории.Rack::MediaType
- для разбора заголовков типа содержимого.Rack::Mime
- для определения типа содержимого на основе расширения файла.
Sinatra
Sinatra — это легковесный веб-фреймворк, построенный на основе Rack. Он предлагает простой и элегантный способ создания веб-приложений, предоставляя разработчикам возможность быстро разрабатывать RESTful API и небольшие веб-приложения.
Sinatra имеет минималистичный синтаксис, что делает его идеальным выбором для небольших проектов и прототипов. Фреймворк позволяет добавлять только необходимые компоненты, что позволяет создавать приложения, максимально соответствующие вашим требованиям. Также он поддерживает использование middleware, что позволяет комбинировать различные библиотеки и улучшать функциональность вашего приложения.
Пример простого приложения на Sinatra
Создаем директорию проекта:
mkdir sinatra-app
cd sinatra-app
bundle init
Добавляем зависимости в Gemfile:
source 'https://rubygems.org'
gem 'puma'
gem 'rackup'
gem 'sinatra'
Создаем app.rb - точку входа в наше приложение:
require 'sinatra'
get '/' do
'Hello, world!'
end
get '/hello/:name' do
"Hello, #{params['name']}!"
end
Запускаем:
ruby app.rb
== Sinatra (v4.1.1) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Puma version: 6.5.0 ("Sky's Version")
* Ruby version: ruby 3.3.4 (2024-07-09 revision be1089c8ec) [x86_64-linux]
* Min threads: 0
* Max threads: 5
* Environment: development
* PID: 138933
* Listening on http://127.0.0.1:4567
* Listening on http://[::1]:4567
Use Ctrl-C to stop
Наше приложение будет доступно по адресу http://localhost:4567, и на странице будет выведена строка Hello, World!
Заключение
В этом уроке мы познакомились с основами Rack, его компонентами и концепцией middleware. Мы также рассмотрели Sinatra как легковесный фреймворк, построенный на основе Rack.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.