Skip to content

Instantly share code, notes, and snippets.

@psylone
Last active October 14, 2016 16:33
Show Gist options
  • Save psylone/b45ee0022bc021525494df02ac6fd4d9 to your computer and use it in GitHub Desktop.
Save psylone/b45ee0022bc021525494df02ac6fd4d9 to your computer and use it in GitHub Desktop.
Задания к занятию 9

Задания к занятию 9

  • Спецификация Rack
  • Rack приложения
  • Rack Middleware
  • Основные компоненты и структура фреймворка

Спецификация Rack

Для создания универсального интерфейса между веб серверами/серверами приложений и Ruby фреймворками была создана спецификация Rack. В общем смысле она включает 3 положения:

  1. Для того чтобы соответствовать спецификации Rack, приложение Ruby должно представлять собой любой объект, отвечающий на метод call. Таким объектом может быть экземпляр класса, класс, модуль или даже Proc объект.

  2. Метод call должен принимать единственный аргумент env (как правило его именуют именно так), в котором содержится хэш с информацией о запросе (включая HTTP заголовки).

  3. Метод call должен возвращать массив из трёх элементов:

  • Статус ответа
  • Хэш заголовков
  • Объект тела ответа, отвечающий на метод each (в самом простом случае это может быть массив)

Пример простейшего Rack приложения:

# rackapp.rb
require 'rack'

app = Proc.new { |env| [200, {}, ['Hey, this is Rack, bro!']] }

Rack::Handler::WEBrick.run app

Middleware (промежуточный слой приложения)

В определённые моменты времени возникают ситуации когда нам требуется выполнить какую-то работу в нашем веб-приложении, и при этом эти задачи не относятся напрямую к логике приложения.

Например, при обработке каждого 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

1. Методы Rack API

Исследуйте исходный код Gem-а Rack и найдите методы:

Для класса Rack::Request:

  • Метод возвращающий название агента пользователя

  • Метод, возвращающий IP адрес пользователя

  • Метод, возвращающий полный URL запроса (включая строку запроса)

Для класса Rack::Response:

  • Метод добавления данных в тело ответа

  • Метод завершающий формирование объекта ответа

  • Метод, позволяющий совершить редирект на указанный URL

2. Rack Middleware

Исследуйте исходный код Gem-а Rack и найдите классы или модули Middleware для:

  • Установки заголовка Content-Type для HTTP ответа на запрос

  • Установки заголовка ответа ETag

3. It's a lobster, sir!

Запустите Rack приложение лобстер, исходный код которого находится внутри Gem-а Rack.

4. Dice Game on Rack

Превратите приложение DiceGame (исходный код в архиве с кодом к заданию) в Rack приложение. Вы можете сделать это на свой вкус, основная идея в том, чтобы управлять ходом игры можно было из клиента, например веб браузера. Следующие положения могут помочь сориентироваться:

  • Используйте различные URL для совершения различных действий. Например, /turn - для совершения хода, /finish - для завершения игры

  • Используйте класс Rack::Request для создания объекта запроса, а затем – метод params для извлечения параметров запроса

  • Воспользуйтесь шаблонизатором ERB (или любым другим, например, Slim) для формирования HTML страниц с ответом на HTTP запрос

Контакты для связи

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment