Написать пост

Руководство по построению HTTP API

Аватар Типичный программист

В руководстве по HTTP API собрана информация по проектированию, основному функционалу, а также о внутреннем API в Heroku.

Рекомендации по проектированию HTTP API, которые почерпнуты из API облачной платформы Heroku. Здесь вы также найдёте информацию о функционале и внутреннем API в Heroku.

Основными целями при построении HTTP API является соблюдение последовательности и концентрация на реализации бизнес-логики. Мы ищем различные, не обязательно самые лучшие, но хорошо документируемые способы разработки API.

  1. Основы
  2. Запросы
  3. Построение пути к ресурсу
  4. Ответы
  5. Артефакты
  6. Выводы

Основы

Принцип разделения ответственности

При проектировании старайтесь сохранять простоту системы, разделяя ответственность между различными частями цикла «запрос-ответ».  При этом простота принимаемых решений позволит концентироваться на решении все более сложных задач. Запросы и ответы выполняются для получения доступа к определенному ресурсу или набору ресурсов. Для определения сущности, которую необходимо получить, используйте путь и тело ответа для передачи содержимого, а заголовки – для передачи метаданных. Можно передавать все параметры в теле запроса, но, как показывает практика, такое решение является менее гибким. Более правильным подходом будет передача части параметров в заголовках.

Требуйте использования защищенных соединений

Для получения данных с помощью HTTP API используйте только защищенные соединения с TLS. Лучше решением было бы отклонять все запросы, не использующие TLS, а именно запросы по http или на 80-ый порт, во избежание небезопасного обмена данными. В случаях, когда это невозможно, отдавайте ответ 403 Forbidden.

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

Требуйте наличие версии в заголовке Accept

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

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

Лучше всего – добавить версию в заголовок вместе с другими метаданными, используя заголовок Accept с пользовательским типом содержимого:

			Accept: application/vnd.heroku+json; version=3
		

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

Включайте заголовок ETags во все запросы, определяя при этом версию возвращаемого ресурса. Это позволит пользователям кэшировать ресурсы и реализовывать условные запросы при помощи использования заголовка If-None-Match, который поможет определить, нужно обновлять кэш или нет.

Используйте Request-ID для интроспекции

Включайте заголовок Request-Id, содержащий в себе UUID значение, в каждый ответ сервера. Регистрируя эти значения на клиенте, сервере или другом сервисе, вы получаете возможность отлаживать и диагностировать проблемы, связанные с запросами.

Разделяйте большие ответы сервера на несколько небольших при помощи заголовка Range

Большие ответы необходимо разбивать на более мелкие, используя заголовок Range. Для получения более детальной информации о заголовках запросов/ответов, кодах состояний и ограничениях изучите использование заголовка Range в API Heroku.

Запросы

Возвращайте соответствующие  коды состояний

Возвращайте соотвествующий код состояния HTTP в каждом ответе. Успешные ответы должны содержать такие коды состояний:

  • 200GET запрос завершился успешно, синхронный DELETE или PATCH запрос завершился успешно или синхронный PUT запрос обновил существующий ресурс.
  • 201 – Синхронный POST запрос завершился, синхронный PUT запрос создал новый ресурс.
  • 202 – Принят POST, PUT, DELETE или PATCH запрос, который будет обработан асинхронно.
  • 206GET запрос завершился успешно, но будет возвращен частичный ответ (см. раздел про заголовок Range).

Обратите внимание на использование кодов состояния ошибок авторизации и аутентификации:

  • 401 Unauthorized – запрос завершился с ошибкой, поскольку пользователь не прошел аутентификацию.
  • 403 Forbidden – запрос завершился с ошибкой, так как пользователь не авторизовался для получения доступа к определенному ресурсу.

Возвращайте соответствующие коды ошибок для предоставления дополнительной информации об их причинах:

  • 422 Unprocessable Entity – Ваш запрос был распознан, но содержит неверные параметры.
  • 429 Too Many Requests – Превышен лимит запросов, попробуйте позже.
  • 500 Internal Server Error – Проблема на стороне сервера, проверьте состояние сайта и/или сообщите о проблеме.

Для получения более подробной информации о кодах состояния HTTP изучите спецификацию.

По возможности, предоставляйте полные версии ресурсов

Возвращайте пользователям вашего API полное представление ресурса (то есть объект со всеми атрибутами) во всех ответах, где это возможно. Всегда предоставляйте полную версию ресурса в ответах на запросы с кодами состояния 200 и 201, включая PUT, PATCH и DELETE запросы.

			$ curl -X DELETE \  
  https://service.com/apps/1f9b/domains/0fd4

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
  "created_at": "2012-01-01T12:00:00Z",
  "hostname": "subdomain.example.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "updated_at": "2012-01-01T12:00:00Z"
}
		

Ответы на запросы с кодом состояния 202 не должны содержать все поля объекта

			$ curl -X DELETE \  
  https://service.com/apps/1f9b/dynos/05bd

HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}
		

API должен принимать сериализованный JSON в теле запроса

И предусматривать возможность передачи сереализованного JSON в теле PUT/PATCH/POST запросов вместо, либо в дополнение к передаваемым данным формы. Таким образом создается симметрия в JSON-ответах:

			$ curl -X POST https://service.com/apps \
    -H "Content-Type: application/json" \
    -d '{"name": "demoapp"}'

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "name": "demoapp",
  "owner": {
    "email": "username@example.com",
    "id": "01234567-89ab-cdef-0123-456789abcdef"
  },
  ...
}
		

Построение пути к ресурсу

Названия ресурсов

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

Действия

Старайтесь проектировать такие конечные url, которые не требуют дополнительных действий для отдельных ресурсов. В случаях, когда это необходимо, добавляйте в общий путь компонент «action» для того, чтобы четко определить эти действия:

			/resources/:resource/actions/:action
		

Например:

			/runs/{run_id}/actions/stop
		

Используйте названия компонентов пути и атрибутов в нижнем регистре

Для названий компонентов пути к ресурсу используйте нижний регистр и разделяйте их при помощи дефиса.

			service-api.com/users 
service-api.com/app-setups
		

Названия атрибутов лучше писать в нижнем регистре, а в качестве разделителя лучше использовать нижнее подчеркивание – таким образом названия полей можно писать без скобок в Javascript:

			service_class: "first"
		

API должен поддерживать доступ к ресурсу не только по его ID

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

			$ curl https://service.com/apps/{app_id_or_name} 
$ curl https://service.com/apps/97addcf0-c182 
$ curl https://service.com/apps/www-prod
		

Сведите к минимуму  количество вложений в пути для доступа к ресурсу

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

			/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}
		

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

			/orgs/{org_id} 
/orgs/{org_id}/apps 
/apps/{app_id} 
/apps/{app_id}/dynos 
/dynos/{dyno_id}
		

Ответы

Предоставляйте UUID запрашиваемых ресурсов

У каждого ресурса по умолчанию должен быть атрибут id. В качестве значений идентификатора ресурса старайтесь всегда использовать UUID. Не используйте идентификаторы, которые не будут уникальными в масштабе вашего сервиса, особенно автоинкрементные идентификаторы.
UUID ресурса выводите в формате 8-4-4-4-12:

			"id": "01234567-89ab-cdef-0123-456789abcdef"
		

Предоставляйте информацию о дате создания и изменения ресурса

По умолчанию ресурс должен хранить информацию о дате его создания created_at и обновления updated_at.

			{ 
    // ... "created_at": "2012-01-01T12:00:00Z", 
    "updated_at": "2012-01-01T13:00:00Z", 
    // ... 
}

		

Временные величины должны быть форматированы согласно ISO8601

Принимайте и возвращайте временные данные только в UTC, а выводите в формате ISO8601:

			"finished_at": "2012-01-01T12:00:00Z"
		

Отношения с внешними сущностями должны быть вынесены во вложенный объект

Внешние отношения должны быть сериализованы как вложенный объект:

			"name": "service-production", 
"owner": { 
  "id": "5d8201b0..." 
 }, // ...
		

А не как поле объекта:

			{ 
  "name": "service-production", 
  "owner_id": "5d8201b0...", 
  // ... 
}
		

Такой подход позволяет добавить больше информации о связанном объекте без необходимости менять структуру ответа:

			{ 
    "name": "service-production", 
    "owner": { 
        "id": "5d8201b0...", 
        "name": "Alice", 
        "email": "alice@heroku.com" 
    }, 
    // ... 
}
		

Создавайте структурированные ответы в случае возникновения ошибок

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

			HTTP/1.1 429 Too Many Requests 
{ 
  "id": "rate_limit", 
  "message": "Account reached its API rate limit.", 
  "url": "https://docs.service.com/rate-limits" 
}
		

Показывайте ограничение по количеству запросов

Ограничение по количеству запросов вводится для поддержания работоспособности системы и возможности качественного обслуживания других клиентов. Для расчета ограничений на количество запросов можно использовать алгоритм текущего ведра. Возвращайте оставшееся количество запросов для каждого запроса в заголовке ответа RateLimit-Remaining.

JSON во всех ответах должен быть минимизирован

Лишний пробел увеличивает размер ответа и многие Javascript клиенты для удобочитаемости автоматически отформатируют JSON. Поэтому лучше минимизировать JSON ответы:

			{ 
  "beta":false, 
  "email":"alice@heroku.com", 
  "id":"01234567-89ab-cdef-0123-456789abcdef", 
  "last_login":"2012-01-01T12:00:00Z", 
  "created_at":"2012-01-01T12:00:00Z", 
  "updated_at":"2012-01-01T12:00:00Z" 
}
		

вместо

			{ 
  "beta": false, 
  "email": "alice@heroku.com", 
  "id": "01234567-89ab-cdef-0123-456789abcdef", 
  "last_login": "2012-01-01T12:00:00Z", 
  "created_at": "2012-01-01T12:00:00Z", 
  "updated_at": "2012-01-01T12:00:00Z" 
}
		

Вы можете опционально добавить возможность получать более развернутый ответ, указывая дополнительный параметр (например, ?pretty=true) или задавая значения для заголовка Accept (Accept: application/vnd.heroku+json; version=3; indent=4;).

Артефакты

Предоставляйте удобную для обработки JSON-схему

Для точного описания вашего API предоставляйте JSON-схему. Для управления схемой используйте prmd, также удостоверьтесь в том, что она проходит валидацию при помощи команды prmd verify.

Предоставляйте удобочитаемую документацию

Если вы создали JSON-схему, используя prmd, как описано выше, вы можете легко сгенерировать Markdown документацию для всех конечных url, используя команду prmd doc.

Вдобавок к описанию конечных url, предоставьте обзор API, включая туда следующую информацию:

  • процесс аутентификации – получение и использование пользовательского токена;
  • стабильность API и его версию, а также информацию о том, как выбрать нужную версию API;
  • общие заголовки запросов и ответов;
  • формат выдачи ошибки;
  • примеры использования API с клиентами на разных языках;

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

Предоставляйте примеры запросов, которые пользователи могут протестировать. Для тестирования этих запросов пользователь должен выполнить минимум действий:

			$ export TOKEN=... # acquire from dashboard 
$ curl -is https://$TOKEN@service.com/users
		

Если вы используете prmd для создания документации, то такие примеры будут сгенерированы автоматически для каждого конечного url.

Опишите стабильность вашего API

Вы можете описать степень стабильности API или отдельных конечных url при помощи установки флагов prototype/development/production.

Для получения дополнительной информации, вы можете изучить документ Политика совместимости Heroku API.

Как только вы объявили API готовым к релизу и стабильным, не стоит совершать модификаций, которые нарушают обратную совместимость внутри этой версии. Для внесения таких изменений создайте новою ветвь API с новым индексом версии.

Выводы

Разобрались, что HTTP API позволяет получить программный доступ к функциям некоторых веб-приложений, затронули запросы, ответы и артефакты. Для полного погружения держите советы по разработке REST API.

Перевод статьи «HTTP API Design Guide»

Следите за новыми постами
Следите за новыми постами по любимым темам
32К открытий32К показов