Работа с API

Предпочтительный формат

В своей практике мы используем JSON-RPC 2.0 как при внешнем, так и при внутреннем взаимодействии. Современные приложения и сервисы работают в RPC парадигме, нежели в ресурсной.

REST используется там, где JSON-RPC 2.0 не сильно подходит: например, работа с файлами (скачивание и загрузка). Внешние системы так же реализуют входящие вебхуки, которые нужно обработать (обычно это REST).

JSON-RPC 2.0

Схема: http://open-rpc.org/

Преимущества:

  • Стандарт со спецификацией
  • У каждого запроса есть идентификатор
  • Режим нотификаций позволяет не ждать ответа от сервера
  • Поддержка батч запросов (отправить несколько запросов в одном)
  • Можно использовать любой транспорт, например сервера очередй (не только HTTP)
  • Единая точка входа
  • JSON формат

Недостатки:

  • Единая точка входа (невозможно настроить кеширование для API через слой выше).
  • JSON формат (возможные проблемы с точки зрения производительности)
  • Нет стриминга (как в GRPC)
  • Слишком просто (для некоторых это фатальный недостаток)

Используемые библиотеки

Процесс разработки

  • Разработчик пишет обычные структуры-сервисы на Go с любой сигнатурой, как будто это библиотечный код.
    func (ns NewsService) Get( ctx context.Context, nf NewsFilter, page, pageSize *int) ([]News, error) {
...
func (ns NewsService) GetByID( ctx context.Context, newsID int) (*News, error) {
  • Запускает go generate – генерируется необходимый файл для zenrpc сервера.
  • После этого разработчик получает:
    • Готовую схему с документацией (из кода)
    • UI для проверки сервиса (который использует схему)
    • Генераторы клиентов под различные языки (Go/JS/Swift/Kotlin/Dart/PHP) - отдает клиентам уже готовые библиотеки.
    • Тулинг для сравнения схем при деплое

RPC Endpoint

Используйте соглашения для внешнего и внутреннего API:

  • /v1/rpc/ – внешнее, доступен префикс сервиса /v1/ наружу через nginx.
  • /int/rpc/ – внутренее, при межсервисном взаимодействии.

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

/v1/rpc/: метод app.Settings

Полезным методом в API, который стоит заложить в начале создания сервиса, является app.Settings. Он определяет поведение клиентов (внешнее API):

  • readonly – находится ли приложение в режиме “только чтение”.
  • maintenance - доступно или недоступно приложение сейчас. Если true, то надо показать экран недоступности приложения с кнопкой попробовать снова (которая перезапросит app.Settings и проверит флаг)
  • features – с помощью этой структуры можно включать или выключать определённые фичи.
  • versions - semver
    • minVersion - минимальная работоспособная версия приложения. Если приложение меньше этой версии, то оно не работает и просит обновиться (с кнопкой обновиться, стор?).
    • warningVersion – показать нотис, то неплохо было бы обновить приложение.
    • invalidVersions – массив заблокированных версий, поведение такое же, как с minVersion.

Использование API клиентами

Клиентами могут быть сервисы, браузеры, nodejs процессы и мобильные приложения. Если мы говорим про внешнее API, то хорошей практикой является разделить хосты API у сайта и приложений.

Важно сохранять cookies от сервера между запросами, потому что в них может быть ценная информация (nginx user id, ddos guard cookies, captcha validation, etc…)

Дополнительные заголовки

Клиенты должны передавать следующие заголовки, а сервер должен их логировать:

  • Platform: iOS/Android/Desktop/Mobile/Web/Widget/etc.
  • Version: версия клиента (v2.0.1,shortHash,etc.)
  • X-Request-Id: сквозной заголовок, который помогает отследить запрос между всеми системами и сервисами. Если ничего не передано, то обычно назначается автоматически.

Конечно же, можно расширить этот список для лучшей идентификации запросов. Если используется система трекинга ошибок (например Setnry), то данные параметры тоже необходимо прокидывать в нее.

Формирование User Agent

Для мобильных клиентов: <приложение>/<версия> (<версия ОС>; <версия устройства>) [версия библиотеки]

Примеры:

  • ru.example.app/2.6.8 (Android 13; M2101K6G; Redmi; sweet)
  • ru.example.app/2.4.11 (iOS 17.1.2; Iphone X) Alamofire/5.8.0

Для сервисов: <сервис> (Version:<версия>) Примеры:

  • apisrv (Version:abcfhc)

API URL

Добавляем в Get параметр вызываемый метод в method или methods

Примеры:

  • /v1/rpc/?method=app.Settings – для одиночного запроса
  • /v1/rpc/?methods=app.Settings,app.Dict – для batch вызовов

Итог

  • Сохранять куки между запросами
  • Передавать дополнительные заголовки: Platform, Version, X-Request-Id
  • Передавать сформированный User-Agent
  • в API URL добавляем GET параметром вызываемые методы

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

Если API доступно в Интернете, то его наверняка попытаются использовать так, как вы не планировали.

Для мобильных клиентов можно использовать SSL Pinning, это затруднит исследование, но оно все еще возможно.

Поэтому проектируйте API таким образом, что о нем знает весь мир. Достоверно понять, что API использует легитимный клиент, практически невозможно. Для реализации проверки подлинности клиента используется техника подписи запросов. Но и это не дает 100% гарантии.

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

  • Используйте лимиты (не должно быть возможности выбрать всю базу целиком за один запрос).
  • Проверяйте пользовательский ввод (пользователь всегда не прав).
  • Используйте SSL Pinning в мобильных клиентах.
  • Используйте подпись запросов для критичных методов.

Мониторинг работы API

Чтобы понять, что API используется не так, как вы планировали, существуют метрики. В библиотеке zenrpc они собираются по умолчанию. По ним необходимо настроить алертинг и смотреть dashboard.

Комбинации заголовков Platform/Version и UserAgent помогут базово определить нелегитимный трафик.