Модуль 2 · Правила HTTP

Справочник «Стандартизация HTTP API» · модуль 2 · правила HTTP

Глава 2.3

2.3

Коды
ответов

Трёхзначное число в первой строке ответа — единственная часть сообщения, которую обязана понимать любая программа. В этой главе разберём, какой код описывает какой исход, чем 401 отличается от 403, а 422 от 400, и как проверить коды тестами.

Категория
Правила HTTP
Уровень
Базовый
Связанные главы
2.2 Методы · 4.1 Problem Details · 4.3 Заголовки контракта
Зачем это знать
Чтобы клиент отличал «повторить позже» от «исправить запрос» без разбора текста

§1Зачем нужен код

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

КлассСмыслЧто делает клиент
2xxзапрос выполненчитает тело или заголовки
3xxнужно дополнительное действиеидёт по адресу из Location или использует кэш
4xxпроблема в запросеисправляет запрос; повтор без изменений бесполезен
5xxпроблема на сервереповторяет позже, сообщает об инциденте

Первая цифра задаёт общее поведение, остальные две уточняют случай. Даже незнакомый код 418 клиент отнесёт к ошибкам запроса, а 503 — к временным неполадкам сервера.

§22xx: запрос выполнен

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

200 OKЗапрос выполнен, результат в теле. Возвращают GET и PATCH.есть тело
201 CreatedСоздан новый ресурс. Адрес — в заголовке Location, представление — в теле.создание
204 No ContentВыполнено, возвращать нечего. Ответ DELETE: тела нет совсем.без тела

Рядом стоит код 304 Not Modified — формально из класса 3xx, но встречается он в обычной работе с API. Сервер отвечает им на условный запрос, когда представление не изменилось с версии, указанной клиентом:

условный запрос: копия у клиента актуальна
GET /api/v1/tickets/a8d04cb9-…
If-None-Match: "4fec2a976840743a"

HTTP/1.1 304
etag: "4fec2a976840743a"
# тела нет: клиент использует то, что у него уже есть

Возвращать 200 на каждый успешный запрос — упущенная возможность. Код должен описывать фактический исход: создание, удаление и «ничего не изменилось» выглядят по-разному.

§34xx: исправлять нужно запрос

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

КодКогда возникаетЧто делает клиент
400сообщение нельзя разобрать: сломанный JSONисправляет формат
401нет токена или он неизвестенполучает токен и повторяет
403токен известен, но операция запрещеназапрашивает права; повтор бесполезен
404ресурса с таким адресом нетпроверяет идентификатор
409запрос противоречит текущему состояниючитает состояние и решает
412условие If-Match не выполненоперечитывает ресурс и повторяет правку
422структура верна, данные не проходят проверкуисправляет поля из errors
429превышен лимит запросовждёт Retry-After секунд

Разница между 400 и 422 сбивает с толку чаще прочих. 400 — сервер не смог разобрать сообщение: испорченный JSON, неверная кодировка. 422 — сообщение разобрано, но данные не соответствуют схеме: нет обязательного поля, значение вне допустимого диапазона, передано лишнее свойство.

422: limit больше допустимого
GET /api/v1/tickets?limit=500

HTTP/1.1 422
content-type: application/problem+json

{
  "type": "…/problems/http-422",
  "title": "Validation error",
  "status": 422,
  "detail": "Request does not match the API contract",
  "errors": [{"loc": ["query", "limit"], "msg": "Input should be less than or equal to 100"}]
}

Массив errors — расширение проекта поверх стандартного формата ошибок. Он называет место (query, поле limit) и причину, поэтому клиент может подсветить нужное поле формы, а не показывать общее «ошибка ввода».

§4401 и 403: две разные ситуации

Эти коды путают чаще всех остальных, а разница между ними простая: 401 — сервер не знает, кто перед ним; 403 — знает и отказывает.

401: токен не передан
POST /api/v1/tickets   # без заголовка Authorization

HTTP/1.1 401
www-authenticate: Bearer
content-type: application/problem+json

{"title": "Authentication required",
 "detail": "Provide a valid Bearer token in the Authorization header"}
403: токен известен, прав не хватает
DELETE /api/v1/tickets/a8d04cb9-…
Authorization: Bearer viewer-token

HTTP/1.1 403
content-type: application/problem+json

{"title": "Operation forbidden",
 "detail": "This token can only read tickets"}

Разница важна для поведения клиента. Получив 401, приложение отправляет пользователя на вход: возможно, срок токена истёк. Получив 403, оно показывает «недостаточно прав» — повторный вход ничего не изменит.

Заголовок WWW-Authenticate

Ответ 401 по стандарту сопровождается заголовком WWW-Authenticate: он сообщает, какой способ аутентификации сервер ожидает. В учебном сервисе это Bearer.

§55xx: сервер не справился

Коды 5xx означают, что запрос был правильным, а проблема на стороне сервиса. Самый частый — 500 Internal Server Error.

С этим кодом связаны два ограничения. Во-первых, 500 не заменяет коды 4xx: если обращения нет, это 404, а не «внутренняя ошибка». Во-вторых, тело ответа не должно раскрывать устройство системы.

нельзяОтдать клиенту
  • traceback с именами файлов;
  • текст SQL-запроса;
  • пути файловой системы;
  • значения переменных окружения.
нужноОставить на сервере
  • подробности — в лог;
  • клиенту — короткое описание;
  • идентификатор запроса в ответе и в логе;
  • по нему инцидент и находят.
500 в учебном сервисе
HTTP/1.1 500
content-type: application/problem+json
x-request-id: d7641a75-7d6e-460e-be67-383efdc60d05

{"title": "Internal server error",
 "detail": "Unexpected server error. The incident has been logged."}

В журнале сервера в этот момент записан полный traceback вместе с тем же x-request-id. Если пользователь укажет этот идентификатор в обращении в поддержку, нужная запись находится в журнале поиском по нему.

§6Какой код для какого исхода

В таблице собраны типичные ситуации и коды, которыми на них отвечают.

СитуацияКодПочему не другой
Список получен200есть тело с результатом
Объект создан201200 не сообщает о появлении ресурса
Объект удалён204тела нет, 200 с пустым телом вводит в заблуждение
Копия клиента актуальна304передавать тело заново незачем
Нет токена401403 означал бы, что клиент опознан
Прав не хватает403повторный вход не поможет, 401 ввёл бы в заблуждение
Объекта нет404пустой список с 200 прячет ошибку клиента
Данные не по схеме422400 означает, что сообщение вообще не разобрано
Слишком много запросов429клиенту нужен Retry-After, а не отказ без объяснения
Упало внутри500класс 4xx переложил бы вину на клиента

§7Коды проверяются тестами

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

tests/test_tickets.py · фрагмент
def test_create_without_token_is_401(client):
    response = client.post("/api/v1/tickets", json={"title": "Не работает VPN"})
    assert response.status_code == 401
    assert response.headers["content-type"] == "application/problem+json"
    assert response.headers["WWW-Authenticate"] == "Bearer"

def test_delete_is_204_and_repeat_gives_404(client, writer):
    ticket_id = client.get("/api/v1/tickets").json()["items"][0]["id"]
    first = client.delete(f"/api/v1/tickets/{ticket_id}", headers=writer)
    assert first.status_code == 204
    assert first.content == b""
    assert client.delete(f"/api/v1/tickets/{ticket_id}", headers=writer).status_code == 404

Второй тест заодно фиксирует то, о чём говорилось в главе 2.2: успешное удаление возвращает 204 без тела, а повтор — 404. Если однажды кто-то решит отвечать на удаление кодом 200 с телом, тест упадёт, и изменение не попадёт в основную ветку незамеченным.

pytest
...............................                                          [100%]
31 passed in 0.08s

§8Частые ошибки

200 на ошибкуОтвет 200 с телом {"success": false} заставляет клиента разбирать текст и ломает поведение посредников.
404 вместо 403Иногда «прячут» чужие объекты кодом 404. Это осознанное решение о безопасности, и его записывают в контракт — иначе клиент будет удивлён.
422 и 400 как синонимы400 — сообщение не разобрано, 422 — разобрано, но данные не по схеме. Клиенту нужны разные действия.
Traceback в ответеУдобно при отладке и опасно в production: раскрывает пути, имена файлов и версии библиотек.
Пустой список вместо 404Запрос одного объекта, который вернул 200 и пустой массив, лишает клиента простой проверки.

§9Проверьте себя

  1. Клиент получил 403. Поможет ли ему повторный вход? А если пришёл 401?
  2. Чем 400 отличается от 422? Приведите по одному запросу на каждый случай.
  3. Почему успешное удаление отвечает 204, а не 200?
  4. Какой код вернёт учебный сервис на GET /api/v1/tickets?limit=500 и что будет в теле?
  5. Напишите тест, который проверяет, что создание без токена возвращает 401 и заголовок WWW-Authenticate.
Успех200 — есть тело201 — создано + Location204 — без тела304 — не изменилось
Ошибка клиента400 — не разобрано401 — кто вы?403 — нельзя404 — нет объекта422 — данные не по схеме429 — слишком часто
Ошибка сервера500 — упало внутриподробности — в логклиенту — x-request-id
Проверкаpytest: код + заголовкиконтракт описывает все коды

Справочник «Стандартизация HTTP API» · модуль 2 · глава 2.3Макаров Максим Николаевич

Кадр