Справочник «Стандартизация HTTP API» · модуль 2 · правила HTTP
Глава 2.3
Коды
ответов
Трёхзначное число в первой строке ответа — единственная часть сообщения, которую обязана понимать любая программа. В этой главе разберём, какой код описывает какой исход, чем 401 отличается от 403, а 422 от 400, и как проверить коды тестами.
- Категория
- Правила HTTP
- Уровень
- Базовый
- Связанные главы
- 2.2 Методы · 4.1 Problem Details · 4.3 Заголовки контракта
- Зачем это знать
- Чтобы клиент отличал «повторить позже» от «исправить запрос» без разбора текста
§1Зачем нужен код
Получив ответ, клиент должен решить, что делать дальше: показать данные, отправить пользователя на вход, подсветить ошибку в форме или повторить запрос позже. Определять это по тексту ошибки нельзя: текст меняется, переводится и отличается у разных операций. Для такого решения и нужен код состояния.
| Класс | Смысл | Что делает клиент |
|---|---|---|
2xx | запрос выполнен | читает тело или заголовки |
3xx | нужно дополнительное действие | идёт по адресу из Location или использует кэш |
4xx | проблема в запросе | исправляет запрос; повтор без изменений бесполезен |
5xx | проблема на сервере | повторяет позже, сообщает об инциденте |
Первая цифра задаёт общее поведение, остальные две уточняют случай. Даже незнакомый код 418 клиент отнесёт к ошибкам запроса, а 503 — к временным неполадкам сервера.
§22xx: запрос выполнен
В учебном сервисе используются три кода успеха, и каждый описывает свой исход.
GET и PATCH.есть телоLocation, представление — в теле.создание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 — сообщение разобрано, но данные не соответствуют схеме: нет обязательного поля, значение вне допустимого диапазона, передано лишнее свойство.
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 — знает и отказывает.
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"}
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, оно показывает «недостаточно прав» — повторный вход ничего не изменит.
Ответ 401 по стандарту сопровождается заголовком WWW-Authenticate: он сообщает, какой способ аутентификации сервер ожидает. В учебном сервисе это Bearer.
§55xx: сервер не справился
Коды 5xx означают, что запрос был правильным, а проблема на стороне сервиса. Самый частый — 500 Internal Server Error.
С этим кодом связаны два ограничения. Во-первых, 500 не заменяет коды 4xx: если обращения нет, это 404, а не «внутренняя ошибка». Во-вторых, тело ответа не должно раскрывать устройство системы.
- traceback с именами файлов;
- текст SQL-запроса;
- пути файловой системы;
- значения переменных окружения.
- подробности — в лог;
- клиенту — короткое описание;
- идентификатор запроса в ответе и в логе;
- по нему инцидент и находят.
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 | есть тело с результатом |
| Объект создан | 201 | 200 не сообщает о появлении ресурса |
| Объект удалён | 204 | тела нет, 200 с пустым телом вводит в заблуждение |
| Копия клиента актуальна | 304 | передавать тело заново незачем |
| Нет токена | 401 | 403 означал бы, что клиент опознан |
| Прав не хватает | 403 | повторный вход не поможет, 401 ввёл бы в заблуждение |
| Объекта нет | 404 | пустой список с 200 прячет ошибку клиента |
| Данные не по схеме | 422 | 400 означает, что сообщение вообще не разобрано |
| Слишком много запросов | 429 | клиенту нужен Retry-After, а не отказ без объяснения |
| Упало внутри | 500 | класс 4xx переложил бы вину на клиента |
§7Коды проверяются тестами
Код ответа — часть контракта, а значит, его проверяют автоматически. В учебном проекте для этого есть отдельные тесты: они вызывают операцию и сравнивают код и заголовки с обещанным.
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 с телом, тест упадёт, и изменение не попадёт в основную ветку незамеченным.
............................... [100%] 31 passed in 0.08s
§8Частые ошибки
200 с телом {"success": false} заставляет клиента разбирать текст и ломает поведение посредников.404. Это осознанное решение о безопасности, и его записывают в контракт — иначе клиент будет удивлён.400 — сообщение не разобрано, 422 — разобрано, но данные не по схеме. Клиенту нужны разные действия.200 и пустой массив, лишает клиента простой проверки.§9Проверьте себя
- Клиент получил
403. Поможет ли ему повторный вход? А если пришёл401? - Чем
400отличается от422? Приведите по одному запросу на каждый случай. - Почему успешное удаление отвечает
204, а не200? - Какой код вернёт учебный сервис на
GET /api/v1/tickets?limit=500и что будет в теле? - Напишите тест, который проверяет, что создание без токена возвращает
401и заголовокWWW-Authenticate.
200 — есть тело201 — создано + Location204 — без тела304 — не изменилось400 — не разобрано401 — кто вы?403 — нельзя404 — нет объекта422 — данные не по схеме429 — слишком часто500 — упало внутриподробности — в логклиенту — x-request-idpytest: код + заголовкиконтракт описывает все кодыСправочник «Стандартизация HTTP API» · модуль 2 · глава 2.3Макаров Максим Николаевич