Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Ruby on Rails
Code

Разработка API с Rails

by
Length:LongLanguages:

Russian (Pусский) translation by Marat Amerov (you can also view the original English article)

В настоящее время широко распространена практика использования API (application programming interfaces). Не только крупные службы, такие как Facebook и Twitter, используют их — API очень популярны из-за распространения клиентских фреймворков, таких как React, Angular и многих других. Ruby on Rails следит за этой тенденцией, а в последней версии представлена ​​новая возможность, позволяющая создавать приложения только для API.

Первоначально эта функциональность была выделена в отдельный гем, называемый rails-api, но с момента выпуска Rails 5 она теперь является частью ядра фреймворка. Эта функциональность вместе с ActionCable, вероятно была наиболее ожидаемой, и поэтому сегодня мы обсудим ее.

В этой статье описывается, как создавать Rails-приложения только для API, и объясняется, как структурировать ваши маршруты и контроллеры, отвечать в формате JSON, добавлять сериализаторы и настраивать CORS (совместное использование ресурсов Cross-Origin). Вы также узнаете о некоторых вариантах защиты API и защите от атак.

Исходники из этой статьи доступны на GitHub.

Создание API-Only приложения

Для начала выполните следующую команду:

Эта команда собирается создать новое Rails-приложение, названное RailsApiDemo. Не забывайте, что поддержка опции --api была добавлена ​​только в Rails 5, поэтому убедитесь, что у вас установлена ​​эта или более новая версия.

Откройте Gemfile и обратите внимание, что он намного меньше обычного: такие гемы, как coffe-rails, turbolinks и sass-rails, исчезли.

Файл config/application.rb содержит новую строку:

Это означает, что Rails собирается загрузить меньший набор middleware: например, нет поддержки cookie и сессий. Более того, если вы попытаетесь создать scaffold, представления и ассеты не будут созданы. На самом деле, если вы проверите каталог views/layouts, вы заметите, что файл application.html.erb также отсутствует.

Другим важным отличием является то, что ApplicationController наследует от ActionController::API, а не ActionController::Base.

Это все в целом простое Rails-приложение, которое вы видели много раз. Теперь давайте добавим пару моделей, чтобы у нас было с чем работать:

Ничего необычного здесь не происходит: сообщение с заголовком, тело, ассоциация для пользователя.

Убедитесь, что созданы правильные ассоциации, а также некоторые простые валидации:

models/user.rb

models/post.rb

Шикарно! Следующий шаг - загрузить несколько экземпляров записей во вновь созданные таблицы.

Загрузка демо-данных

Самый простой способ загрузить какие-нибудь данные - это использовать файл seeds.rb внутри каталога db. Тем не менее, я ленив (как и многие программисты) и не хочу думать о каком-либо контенте. Поэтому, почему бы нам не воспользоваться преимуществами гема faker, который может создавать случайные данные разных типов: имена, электронные письма, слова хипстера, тексты «lorem ipsum» и многое другое.

Gemfile

Установка гемов:

Теперь подкорректируйте seeds.rb:

db/seeds.rb

Наконец, загрузите данные:

Ответ с JSON

Теперь, конечно же нам нужны некоторые маршруты и контроллеры для разработки нашего API. Это обычная практика, вложить маршруты API в api/ path. Кроме того, разработчики обычно предоставляют версию API в пути, например api/v1/. Если позже необходимо внести некоторые изменения, вы можете просто создать новое пространство имен (v2) и отдельный контроллер.

Вот например как могут выглядеть ваши маршруты:

config/routes.rb

Это создает такие маршруты:

Вы можете использовать метод scope вместо namespace, но затем по умолчанию он будет искать UsersController и PostsController внутри каталога controllers, а не внутри controllers/api/v1, поэтому будьте осторожны.

Создайте папку api с вложенным каталогом v1 внутри каталога controllers. Заполните его своими контроллерами:

controllers/api/v1/users_controller.rb

controllers/api/v1/posts_controller.rb

Обратите внимание, что вам не только нужно вложить файл контроллера в путь api/v1, но сам класс также должен быть помещен в модули Api и V1.

Следующий вопрос - как правильно формировать ответы с данными в формате JSON? В этой статье мы попытаемся использовать следующие решения: гемы jBuilder и active_model_serializers. Следовательно, прежде чем переходить к следующему разделу, добавьте их в Gemfile:

Gemfile

Затем выполните:

Использование гема jBuilder

JBuilder - популярный гем, поддерживаемый командой Rails, который предоставляет простой DSL (предметно-ориентированный язык), позволяющий Вам определять структуры JSON в ваших представлениях.

Предположим, мы хотели отобразить все сообщения, когда пользователь выполняет действие index:

controllers/api/v1/posts_controller.rb

Все, что вам нужно сделать, это создать представление, названное в соответствии с  действием и расширением .json.jbuilder. Обратите внимание, что представление также должно быть помещено  в api/v1:

views/api/v1/posts/index.json.jbuilder

json.array! проходит по массиву @posts. json.id, json.title и json.body генерируют ключи с соответствующими именами, устанавливая аргументы в качестве значений. Если вы перейдете в http://localhost:3000/api/v1/posts.json, вы увидите результат, похожий на этот:

Что, если мы хотим отобразить автора для каждого сообщения? Это просто:

Вывод будет изменен на:

Содержимое файлов .jbuilder - это простой  код на Ruby , поэтому вы можете использовать все основные операции, как обычно.

Обратите внимание, что jBuilder поддерживает партиалы, как и любой обычное представление Rails, поэтому вы также можете написать:

Затем, создайте файл views/api/v1/posts/_post.json.jbuilder со следующим содержимым:

Итак, как вы видите, jBuilder прост и удобен. Однако в качестве альтернативы вы можете использовать сериалайзеры, поэтому давайте обсудим их в следующем разделе.

Использование сериалайзеров

гем rails_model_serializers был создан командой, которая изначально поддерживала rails-api. Как указано в документации, rails_model_serializers привносит соглашение над конфигурацией для генерации вашего JSON. В основном вы определяете, какие поля следует использовать при сериализации (то есть генерации JSON).

Вот наш первый сериалайзер:

serializers/post_serializer.rb

Здесь мы декларируем, что все эти поля должны присутствовать в полученном JSON. Теперь такие методы, как to_json и as_json, будут использовать эту конфигурацию и возвращать желаемое содержимое.

Чтобы увидеть это в действии, измените действие index следующим образом:

controllers/api/v1/posts_controller.rb

as_json будет автоматически вызван на объекте @posts.

Как насчет пользователей? Как и модели, сериалайзеры позволяют указывать отношения. Более того, сериалайзеры могут быть вложенными:

serializers/post_serializer.rb

Теперь, когда вы сериализуете пост, он автоматически будет содержать вложенный ключ user с его идентификатором и именем. Если позже вы создадите отдельный сериалайзер для пользователя с атрибутом :id, атрибут будет исключен:

serializers/post_serializer.rb

Соответственно @user.as_json не вернет id пользователя. Тем не менее, @post.as_json вернет имя и id пользователя, поэтому имейте это в виду.

Безопасность API

Во многих случаях мы не хотим, чтобы кто угодно выполнял какие-либо действия с использованием API. Итак, давайте добавим простую проверку безопасности и заставим наших пользователей отправлять свои токены при создании и удалении постов.

Токен будет иметь неограниченный срок службы и будет создан при регистрации пользователя. Прежде всего, добавьте новый столбец token в таблицу users:

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

db/migrate/xyz_add_token_to_users.rb

Применим миграцию:

Теперь добавим коллбек before_save:

models/user.rb

Приватный метод generate_token будет создавать токен в бесконечном цикле и проверять, является ли он уникальным или нет. Как только будет найден уникальный токен, вернем его:

models/user.rb

Вы можете использовать другой алгоритм для генерации токена, например, на основе хеша MD5 имени пользователя и некоторой соли.

Регистрация пользователя

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

controllers/api/v1/users_controller.rb

Возвращать коды состояния HTTP - хорошая идея, чтобы разработчики точно понимали, что происходит. Теперь вы можете либо предоставить новый сериалайзер для пользователей, либо использовать файл .json.jbuilder. Я предпочитаю последний вариант (поэтому я не передаю вариант :json для метода render), но вы можете выбрать любой из них. Обратите внимание, что токен не всегда должен быть сериализован, например, когда вы возвращаете список всех пользователей - его следует скрыть!

views/api/v1/users/create.json.jbuilder

Следующий шаг - проверить, все ли работает нормально. Вы можете использовать команду curl или написать код на Ruby. Поскольку эта статья посвящена Ruby, я перейду к написанию кода.

Тестирование регистрации пользователя

Чтобы выполнить HTTP запрос, мы будем использовать гем Faraday, который обеспечивает общий интерфейс для многих адаптеров (по умолчанию это Net::HTTP). Создайте отдельный файл Ruby, включите в него Faraday и настройте клиент:

api_client.rb

Все эти параметры довольно понятны: мы выбираем адаптер по умолчанию, устанавливаем URL-адрес запроса http://localhost:3000/api/v1/users, меняем тип контента на application/json и предоставляем тело нашего запроса

Ответ сервера будет содержать JSON, поэтому для его парсинга я буду использовать гем Oj:

api_client.rb

Помимо спарсеного ответа, я также показываю код состояния с целью отладки.

Теперь вы можете просто запустить этот скрипт:

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

Аутентификация с помощью токена

Чтобы обеспечить аутентификацию с токеном, будем использовать метод authenticate_or_request_with_http_token. Он является частью модуля ActionController::HttpAuthentication::Token::ControllerMethods, поэтому не забудьте включить его:

controllers/api/v1/posts_controller.rb

Добавьте новое before_action и соответствующий метод:

controllers/api/v1/posts_controller.rb

Теперь, если токен не установлен или если пользователь с таким токеном не может быть найден, будет возвращена ошибка 401, останавливая выполнения действия.

Обратите внимание, что связь между клиентом и сервером должна выполняться через HTTPS, поскольку в противном случае токены могут быть легко подделаны. Конечно, предоставленное решение не является идеальным, и во многих случаях предпочтительнее использовать протокол OAuth 2 для аутентификации. Есть по крайней мере два гема, которые значительно упрощают процесс поддержки этого функционала: Doorkeeper и oPRO.

Создание постов

Чтобы увидеть нашу аутентификацию в действии, добавьте действие create в PostsController:

controllers/api/v1/posts_controller.rb

Здесь мы используем сериалайзер для отображения соответствующего JSON. @user уже был установлен внутри before_action.

Теперь все проверьте, используя этот простой код:

api_client.rb

Замените аргумент, переданный в token_auth, на токен, полученный при регистрации, и запустите скрипт.

Удаление постов

Удаление поста выполняется таким же образом. Добавьте действие destroy:

controllers/api/v1/posts_controller.rb

Мы разрешаем пользователям удалять записи, которыми они владеют. Если сообщение успешно удалено, будет возвращен код статуса 204 (без содержимого). В качестве альтернативы вы можете отправить id поста, который был удален, поскольку он по-прежнему будет доступен из памяти.

Вот фрагмент кода для проверки этого нового функционала:

api_client.rb

Замените идентификатор поста на число, которое работает для вас.

Настройка CORS

Если вы хотите включить другие веб-службы для доступа к вашему API (с клиентской стороны), необходимо правильно настроить CORS (совместное использование ресурсов Cross-Origin). В принципе, CORS позволяет веб-приложениям отправлять запросы AJAX сторонним службам. К счастью, есть гем, называемый rack-cors, который позволяет нам легко все настроить. Добавьте его в Gemfile:

Gemfile

Установка:

А затем укажите конфигурацию внутри файла config/initializers/cors.rb. Фактически, этот файл уже создан для вас и содержит пример использования. Вы также можете найти довольно подробную документацию на странице гема.

Например, следующая конфигурация позволит любому пользователю получить доступ к вашему API с помощью любого метода:

config/initializers/cors.rb

Предотвращение атак

Последнее, что я расскажу в этом руководстве, - это защита вашего API от атак типа «abuse» и «denial of service attacks». Существует прекрасный гем, называемый rack-attack (созданный разработчиками Kickstarter), который позволяет вам делать черные или белые списки клиентов, предотвращать захлебывание сервера запросами и т. д.

Добавим гем в Gemfile:

Gemfile

Установка:

А затем предоставите конфигурацию внутри файла инициализации rack_attack.rb. В документации к гему перечислены все доступные варианты и предлагаются некоторые практические примеры использования. Вот пример конфигурации, который ограничивает всем, кроме вас, максимальное количество запросов до 5 в секунду:

config/initializers/rack_attack.rb

Еще одно действие, которое должно быть выполнено - это включение RackAttack в качестве middleware:

config/application.rb

Заключение

Мы подошли к концу статьи. Надеюсь, что к настоящему времени вы почувствуете себя более уверенно в разработке API с Rails! Помните, что это не единственный доступный вариант - еще одно популярное решение, которое существует в течение довольно долгого времени, - это Grape framework, поэтому вы также можете его попробовать.

Не стесняйтесь задавать свои вопросы, если вам что-то неясно. Я благодарю вас за то, что оставались со мной, и счастливого кодинга!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.