Зарегистрируйтесь, чтобы продолжить обучение

Rack Ruby On Rails

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.


Дополнительные материалы

  1. Курс Протокол HTTP
  2. Тестовые методы Rack
  3. Репозиторий Rack
  4. Sinatra
  5. Пример Hello, World! на Sinatra

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff