Использование Rails для API-приложений

В этом руководстве вы узнаете

  • Что предоставляет Rails для API-приложений
  • Как сконфигурировать Rails для работы без браузерных особенностей
  • Как решить, какие промежуточные программы нужно включить
  • Как решить, какие модули использовать в контроллере

1. Что такое API-приложение?

Обычно, когда говорят, что Rails используется как "API", имеется в виду предоставление программно доступного API вместе с веб-приложением. Например, GitHub предоставляет API, который можно использовать в собственном клиенте.

С приходом клиентских фреймворков многие разработчики используют Rails для создания бэкенда, общего для их веб-приложений и других нативных приложений.

Например, Twitter использует свой публичный API в своем веб-приложении, который создан как статичный сайт, потребляющий ресурсы JSON.

Вместо использования Rails для генерации HTML, взаимодействующего с сервером с помощью форм и ссылок, многие разработчики трактуют их веб-приложения как всего лишь клиент API, созданный из HTML с помощью JavaScript, обращающегося к JSON API.

Это руководство раскрывает создание приложения Rails, отдающего JSON-ресурсы клиентам API, включая клиентский фреймворк.

2. Зачем использовать Rails для JSON API?

Первый вопрос, который многие задают, когда думают о создании JSON API с помощью Rails, это: "Не будет ли использование Rails для отдачи некоторого JSON избыточным? Не должен ли я использовать что-то вроде Sinatra?".

Для очень простых API это может быть истиной. Однако, даже в самых навороченных HTML приложениях, большинство логики приложения находится вне уровня представления.

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

Давайте посмотрим на некоторые вещи, которые Rails предоставляет из коробки и которые применимы к API-приложениям.

На уровне промежуточных программ:

  • Перезагрузка: приложения Rails поддерживают прозрачную перезагрузку. Это работает, даже если ваше приложение становится большим и рестарт сервера для каждого запроса становится неприемлемым.
  • Режим разработки: приложения Rails идут с разумными значениями по умолчанию для разработки, что делает разработку приятной без ущерба производительности для production.
  • Тестовый режим: то же самое, что и для режима разработки.
  • Логирование: приложения Rails логируют каждый запрос с уровнем детализации, приемлемым для текущего режима. Логи Rails в development включают информацию о среде запроса, запросах в базу данных и основную информацию о производительности.
  • Безопасность: Rails обнаруживает и мешает исполнению IP-спуфинга, и безопасным способом обрабатывает криптографические сигнатуры в атаках по времени. Не знаете, что такое IP-спуфинг или атака по времени? Вот-вот!
  • Парсинг параметров: Хотите устанавливать ваши параметры как JSON вместо URL-кодированной строки? Без проблем. Rails декодирует JSON и сделает его доступным в params. Хотите использовать вложенные URL-кодированные параметры? Это тоже работает.
  • Условный GETs: Rails поддерживает условный GET (ETag и Last-Modified), обрабатывая заголовки запроса и возвращая правильный отклик и код статуса. Все, что нужно, это использовать проверку stale? в вашем контроллере, и Rails позаботится обо всех деталях HTTP.
  • Запросы HEAD: Rails прозрачно конвертирует запросы HEAD в GET, и возвращает только заголовки тем же образом. Это позволяет HEAD надежно работать во всех API Rails.

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

На уровне Action Pack:

  • Ресурсный роутинг: Если вы создаете RESTful JSON API, вам захочется использовать роутер Rails. Чистое и общепринятое сопоставление от HTTP к контроллерам означает, что не нужно тратить время, думая, как смоделировать ваш API в терминах HTTP.
  • Генерация URL: Обратной стороной роутинга является генерация URL. Хороший API, основанный на HTTP, включает URL (в качестве примера смотрите GitHub Gist API).
  • Отклики с заголовками и редиректами: head :no_content и redirect_to user_url(current_user) очень удобны. Конечно, заголовки отклика можно добавить руками, но зачем?
  • Кэширование: Rails предоставляет кэширование страниц, экшнов и фрагментов. Кэширование фрагментов особенно полезно при создании вложенных объектов JSON.
  • Базовая, дайджестная и токенная аутентификация: Rails поставляется с поддержкой из коробки трех типов аутентификации HTTP.
  • Инструментарий: в Rails имеется инструментальный API, запускающий зарегистрированные обработчики для множества событий, таких как обработка экшна, отсылка файла или данных, перенаправление и запросы к базе данных. Полезная нагрузка о каждом событии приходит с соответствующими параметрами (для события обработки экшна полезная нагрузка включает контроллер, экшн, параметры, формат запроса, метод запроса и полный путь запроса).
  • Генераторы: Часто удобно сгенерировать ресурс и получить модель, контроллер, заготовки для тестов и роутов, созданные одной командой, для дальнейшей доработки. То же самое для миграций и остального.
  • Плагины: Многие сторонние библиотеки поставляются с поддержкой Rails, что уменьшает или устраняет стоимость настройки и внедрения библиотеки во фреймворк. Это включает вещи, такие как переопределение генераторов по умолчанию, добавление задач Rake и принятие выбора в Rails (такого как логгер и кэширующий бэкенд).

Конечно, процесс загрузки Rails также соединяет воедино все зарегистрированные компоненты. Например, процесс загрузки Rails это то, что использует файл config/database.yml при конфигурации Active Record.

Краткая версия: можно не задумываться, какие части Rails все еще применимы, даже если вы уберете уровень представления, ответом будет - большая часть из них.

3. Базовая конфигурация

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

3.1. Создание нового приложения

Можно сгенерировать новое приложение api Rails:

$ rails new my_api --api

Это сделает три основных вещи:

  • Сконфигурирует приложение, чтобы оно запускало более ограниченный набор промежуточных программ, чем обычно. В частности, оно не включит по умолчанию какие-либо промежуточные программы, полезные для браузерных приложений (такие как поддержка куки).
  • Унаследует ApplicationController от ActionController::API вместо ActionController::Base. Как и в случае с промежуточными программами, это выкинет все модули Action Controller, предоставляющие функциональность, в основном используемую браузерными приложениями.
  • Сконфигурирует генераторы, чтобы они пропускали генерацию вью, хелперов и ассетов при генерации нового ресурса.

3.2. Изменение существующего приложения

Если хотите взять существующее приложение и сделать его API-приложением, следуйте этим шагам.

В config/application.rb добавьте следующую строчку в самый верх определения класса Application:

config.api_only = true

В config/environments/development.rb установите config.debug_exception_response_format, чтобы настроить формат, используемый в откликах, когда происходит ошибка в режиме development.

Чтобы отрендерить страницу HTML с отладочной информацией, используйте значение :default.

config.debug_exception_response_format = :default

Чтобы отрендерить отладочную информацию, сохранив формат отклика, используйте значение :api.

config.debug_exception_response_format = :api

По умолчанию config.debug_exception_response_format установлен :api, когда config.api_only установлен true.

Наконец, в app/controllers/application_controller.rb вместо:

class ApplicationController < ActionController::Base
end

сделайте:

class ApplicationController < ActionController::API
end

4. Выбор промежуточных программ

API-приложение поставляется со следующими промежуточными программами по умолчанию:

  • ActionDispatch::HostAuthorization
  • Rack::Sendfile
  • ActionDispatch::Static
  • ActionDispatch::Executor
  • ActionDispatch::ServerTiming
  • ActiveSupport::Cache::Strategy::LocalCache::Middleware
  • Rack::Runtime
  • ActionDispatch::RequestId
  • ActionDispatch::RemoteIp
  • Rails::Rack::Logger
  • ActionDispatch::ShowExceptions
  • ActionDispatch::DebugExceptions
  • ActionDispatch::ActionableExceptions
  • ActionDispatch::Reloader
  • ActionDispatch::Callbacks
  • ActiveRecord::Migration::CheckPending
  • Rack::Head
  • Rack::ConditionalGet
  • Rack::ETag

Смотрите раздел по внутренним промежуточным программам руководства по Rack, чтобы узнать подробности о них.

Другие плагины, включая Active Record, могут добавлять дополнительные промежуточные программы. В основном, эти промежуточные программы безразличны к типу создаваемого приложения, и имеют смысл в API-приложении Rails.

Можно получить список всех промежуточных программ вашего приложения с помощью:

$ bin/rails middleware

4.1. Использование кэширующей промежуточной программы

По умолчанию Rails добавит промежуточную программу, предоставляющую хранилище кэша, основанного на конфигурации вашего приложения (по умолчанию memcache). Это означает, что встроенный кэш HTTP будет полагаться на нее.

Например, используя метод stale?:

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at)
    render json: @post
  end
end

Вызов stale? сравнит заголовок If-Modified-Since в запросе с @post.updated_at. Если заголовок новее, чем время последнего модифицирования, этот экшн вернет отклик "304 Not Modified". В противном случае, он отрендерит отклик и включит в него заголовок Last-Modified.

Обычно этот механизм используется отдельно для каждого клиента. Кэширующая промежуточная программа позволяет распределять этот кэширующий механизм между клиентами. Можно включить межклиентское кэширование в вызове stale?:

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at, public: true)
    render json: @post
  end
end

Это означает, что кэширующая промежуточная программа сохранит значение Last-Modified для URL в кэше Rails, и добавит заголовок If-Modified-Since в любой последующий входящий запрос к этому URL.

Воспринимайте это как кэширование страниц в семантике HTTP.

4.2. Использование Rack::Sendfile

При использовании метода send_file в контроллере Rails, он устанавливает заголовок X-Sendfile. Rack::Sendfile ответственен за фактическую отсылку файла.

Если ваш фронтенд сервер поддерживает ускоренную отсылку файла, Rack::Sendfile переложит работу по фактической отсылке файла на фронтенд сервер.

Можно настроить имя заголовка, которое использует ваш фронтенд сервер для этой цели, с помощью config.action_dispatch.x_sendfile_header в соответствующем среде конфигурационном файле.

Подробнее узнать о том, как использовать Rack::Sendfile с популярными фронтендами можно в документации Rack::Sendfile.

Вот несколько значений этого заголовка для некоторых популярных серверов, которые, как только эти серверы будут настроены, добавят поддержку для ускоренной отсылки файла:

# Apache и lighttpd
config.action_dispatch.x_sendfile_header = "X-Sendfile"

# Nginx
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"

Убедитесь, что сконфигурировали на своем сервере поддержку этих опций в соответствии с инструкциями в документации Rack::Sendfile.

4.3. Использование ActionDispatch::Request

ActionDispatch::Request#params примет параметры от клиента в формате JSON и сделает их доступными в контроллере внутри params.

Для его использования клиенту нужно сделать запрос с кодированными в JSON параметрами и указать Content-Type как application/json.

Вот пример на jQuery:

jQuery.ajax({
  type: 'POST',
  url: '/people',
  dataType: 'json',
  contentType: 'application/json',
  data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }),
  success: function(json) { }
});

ActionDispatch::Request увидит Content-Type и вашими параметрами будут:

{ :person => { :firstName => "Yehuda", :lastName => "Katz" } }

4.4. Использование промежуточных программ для сессий

Следующие промежуточные программы, используемые для управления сессией, исключены из приложений API, так как им обычно не нужны сессии. Если один из ваших клиентов API это браузер, возможно, вы захотите вернуть одну из:

  • ActionDispatch::Session::CacheStore
  • ActionDispatch::Session::CookieStore
  • ActionDispatch::Session::MemCacheStore

Трудность в их возврате в том, что по умолчанию при добавлении они передают session_options (включая ключ сессии), поэтому нельзя просто добавить инициализатор session_store.rb, добавить use ActionDispatch::Session::CookieStore и получить функционирующие сессии. (Проясним: сессии может и будут работать, но опции сессии будут игнорироваться - т.е. будет ключ сессии по умолчанию _session_id)

Вместо этого инициализатора нужно установить нужные опции где-то до создания стека промежуточных программ (наподобие config/application.rb) и передать их в предпочитаемую промежуточную программу, наподобие:

# Это также сконфигурирует session_options для использования ниже
config.session_store :cookie_store, key: '_interslice_session'

# Требуется для всех управлений сессиями (независимо от session_store)
config.middleware.use ActionDispatch::Cookies

config.middleware.use config.session_store, config.session_options

4.5. Другие промежуточные программы

Rails поставляется с рядом других промежуточных программ, которые вы, возможно, захотите использовать в API-приложении, особенно если одним из клиентов вашего API является браузер:

  • Rack::MethodOverride
  • ActionDispatch::Cookies
  • ActionDispatch::Flash

Любые из этих промежуточных программ могут быть добавлены с помощью:

config.middleware.use Rack::MethodOverride

4.6. Удаление промежуточных программ

Если вы не хотите использовать промежуточную программу, которая включена по умолчанию в набор промежуточных программ для API, ее можно убрать с помощью:

config.middleware.delete ::Rack::Sendfile

Учтите, что удаление этих промежуточных программ удалит поддержку для определенных особенностей в Action Controller.

5. Выбор модулей контроллера

API-приложение (использующее ActionController::API) по умолчанию поставляется со следующими модулями:

  • ActionController::UrlFor: Делает доступными url_for и подобные хелперы.
  • ActionController::Redirecting: Поддержка для redirect_to.
  • AbstractController::Rendering и ActionController::ApiRendering: Базовая поддержка для рендеринга.
  • ActionController::Renderers::All: Поддержка для render :json и сотоварищей.
  • ActionController::ConditionalGet: Поддержка для stale?.
  • ActionController::BasicImplicitRender: Убеждается, что возвращен пустой отклик, если нет явного.
  • ActionController::StrongParameters: Поддержка для фильтрации параметров в сочетании с массовым назначением Active Model.
  • ActionController::DataStreaming: Поддержка для send_file и send_data.
  • AbstractController::Callbacks: Поддержка для before_action и подобных хелперов.
  • ActionController::Rescue: Поддержка для rescue_from.
  • ActionController::Instrumentation: Поддержка для инструментальных хуков, определенных Action Controller (подробности относительно этого смотрите в руководстве Инструментарий Active Support).
  • ActionController::ParamsWrapper: Оборачивает хэш параметров во вложенный хэш, таким образом, к примеру, не нужно указывать корневые элементы при посылка запросов POST.
  • ActionController::Head: Поддержка возврата отклика без тела сообщения, только заголовки.

Другие плагины могут добавлять дополнительные модули. Список всех модулей, включенных в ActionController::API можно получить в консоли rails:

irb> ActionController::API.ancestors - ActionController::Metal.ancestors
=> [ActionController::API,
    ActiveRecord::Railties::ControllerRuntime,
    ActionDispatch::Routing::RouteSet::MountedHelpers,
    ActionController::ParamsWrapper,
    ... ,
    AbstractController::Rendering,
    ActionView::ViewPaths]

5.1. Добавление других модулей

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

Некоторые распространенные модули, которые вы, возможно, захотите добавить:

  • AbstractController::Translation: Поддержка для методов локализации l и перевода t.
  • Поддержка для базовой, дайджестной или токенной аутентификации HTTP:
    • ActionController::HttpAuthentication::Basic::ControllerMethods
    • ActionController::HttpAuthentication::Digest::ControllerMethods
    • ActionController::HttpAuthentication::Token::ControllerMethods
  • ActionView::Layouts: Поддержка для макетов при рендеринге.
  • ActionController::MimeResponds: Поддержка для respond_to.
  • ActionController::Cookies: Поддержка для cookies, что включает поддержку для подписанных и зашифрованных куки. Он требует промежуточную программу для куки.
  • ActionController::Caching: Поддержка кэширования вью для контроллера API. Отметьте, что нужно вручную указать хранилище кэша внутри контроллера подобно следующему:

    class ApplicationController < ActionController::API
      include ::ActionController::Caching
      self.cache_store = :mem_cache_store
    end
    

Rails не передает эту конфигурацию автоматически.

Лучшим местом для добавления модулей является ApplicationController, но вы также можете добавить модули в отдельные контроллеры.