Содержание статьи
- Введение
- Ошибка – Возврат “200 OK” при частичных ошибках
- Ошибка – Использование “500 Internal Server Error” при сбое удалённого API
- Ошибка – Скрытие “403 Forbidden” под “404 Not Found”
- Ошибка – Возврат “200 OK” вместо “201 Created” при создании ресурса
- Ошибка – Возврат “200 OK” при логических ошибках
- Ошибка – Игнорирование “406 Not Acceptable” при неподдерживаемом Accept-заголовке
- Ошибка – Использование “400 Bad Request” вместо “415 Unsupported Media Type”
- Ошибка – Игнорирование “304 Not Modified” при отсутствии изменений
- Ошибка – Возврат “404 Not Found” при использовании неподдерживаемого HTTP-метода
- Ошибка – Возврат “200 OK” при DELETE-запросе, если ресурс не найден
- Ошибка – Отсутствие заголовка “Location” при “201 Created”
- Ошибка – Возврат “500 Internal Server Error” при ошибке клиента
- Ошибка – Возврат “500 Internal Server Error” при недоступности базы данных
- Ошибка – Возврат “200 OK” при сбое внешнего API
- Ошибка – Отсутствие заголовка “Content-Type” в ответе
- Ошибка – Использование “401 Unauthorized” при истёкшем токене
- Ошибка – Возврат “200 OK” при ошибках в query-параметрах
- Ошибка – Игнорирование “ETag” в GET-запросах
- Ошибка – Неиспользование “202 Accepted” для асинхронных операций
- Ошибка – Ошибки обработки “OPTIONS”-запросов при CORS
- Ошибка – Использование “302 Found” после POST-запросов
- Ошибка – Возврат “500 Internal Server Error” при ошибке CORS
- Ошибка – Отсутствие заголовка “Allow” при “405 Method Not Allowed”
- Ошибка – Использование “403 Forbidden” вместо “401 Unauthorized”
- Ошибка – Возврат “400 Bad Request” при дублирующемся ресурсе вместо “409 Conflict”
- Ошибка – Возврат “200 OK” при неудачной аутентификации
- Ошибка – Неверный статус при загрузке файлов: “200 OK” вместо “201” или “202”
- Ошибка – Отсутствие заголовка “Retry-After” при “503 Service Unavailable”
- Ошибка – Неиспользование “422 Unprocessable Entity” при ошибках валидации
- Ошибка – Игнорирование “429 Too Many Requests” при превышении лимитов запросов
- Почему 22 из 30 пунктов считаются обоснованными best practices
- Спорные пункты (сводная таблица)
- “Строгость” рекомендаций (сводная таблица)
- Вывод
Введение
Консультирую для крупных компании и команды в оптимизации архитектуры, устранении узких мест и снижении стоимости владения IT-продуктами я регулярно сталкиваюсь с тем, что даже в зрелых командах допускаются одни и те же ошибки при работе с HTTP-статусами в REST API.
На первый взгляд, может показаться, что статус ответа сервера – это формальность. Но на практике именно некорректно выбранные HTTP-коды:
- усложняют клиентскую логику,
- затрудняют отладку,
- приводят к ошибочным выводам в мониторинге,
- маскируют реальные причины проблем,
- и, в итоге, увеличивают затраты на сопровождение продукта.
Эта статья – не просто теория. Это 30 реальных ошибок, которые я наблюдал в промышленных окружениях. Для каждой из них я постараюсь объяснить:
- В чём заключается ошибка,
- Пример, где это вызвало сложности,
- Почему это негативно влияет на продукт,
- Как рекомендуется делать,
- Почему именно это улучшает архитектуру и разработку в целом.
- Пример запроса
- Является ли этот пункт спорным
Также в конце статьи вы найдёте таблицу, в которой указано, какие рекомендации строго регламентированы спецификациями (RFC), какие являются best practices, а где следует принимать решение в зависимости от контекста.
Если вы проектируете REST API или отвечаете за сопровождение распределённой системы – это руководство сэкономит вам часы отладки, десятки сообщений в Telegram/Slack и сотни строк избыточного кода.
1. “200 OK” при частичных ошибках
Проблема: Сервер возвращает “200 OK”, даже если не все операции были успешными.
Пример из жизни: Клиент отправляет массив заказов на создание. Один из них содержит недопустимые данные. Сервер создаёт 4 из 5, но возвращает “200 OK”.
В чем именно проблема: Клиент не знает, что один из заказов не создан. Это приводит к потере данных и ошибкам в UI.
Рекомендация: Возвращать “207 Multi-Status” или JSON с перечислением успешных и неуспешных элементов.
В чем именно улучшение: Клиент получает прозрачную информацию о том, что именно обработано.
Пример запроса:
POST /orders/bulk HTTP/1. 1
Content-Type: application/json
[
{"item": "apple", "quantity": 5},
{"item": "banana", "quantity": -2}, // ошибка
{"item": "milk", "quantity": 1}
]
Спорный момент: нет
2. “500 Internal Server Error” при удалённом API
Проблема: API возвращает “500”, если сторонний сервис не отвечает.
Пример из жизни: Ваш API запрашивает курсы валют с внешнего ресурса, который временно недоступен. Клиент получает “500”.
В чем именно проблема: Клиент думает, что ошибка на вашем сервере.
Рекомендация: Возвращать “502 Bad Gateway” или “504 Gateway Timeout”.
В чем именно улучшение: Клиенту ясно, что сбой произошёл не на вашей стороне.
Пример запроса:
GET /exchange-rates HTTP/1. 1
Accept: application/json
Спорный момент: нет
3. Скрытие “403 Forbidden” ошибок за “404 Not Found”
Проблема: Сервер возвращает “404”, если пользователь не имеет доступа к ресурсу.
Пример из жизни: У сотрудника нет прав на просмотр “/admin/metrics”, но он получает “404”, хотя путь существует.
В чем именно проблема: Пользователь не понимает, что ему не хватает прав.
Рекомендация: Использовать “403 Forbidden”, если ресурс есть, но доступ ограничен.
В чем именно улучшение: Клиенту понятно, что он авторизован, но у него нет нужных прав.
Пример запроса:
GET /admin/metrics HTTP/1. 1
Authorization: Bearer eyJhbGci...
Спорный момент:
Иногда уместно «маскировать» наличие ресурса (особенно в безопасности – чтобы не раскрывать структуру API).
Контекст:
- Если API – публичное и неавторизованное: “404” может быть разумным выбором.
- Если пользователь авторизован и ожидает результат – “403” лучше.
Вывод:
“403” уместен для доверенных клиентов, “404” – для безопасности в zero-knowledge API.
4. Использование “200 OK” при создании ресурса вместо “201 Created”
Проблема: После POST-запроса сервер возвращает “200 OK”.
Пример из жизни: Клиент отправляет данные нового пользователя. Ресурс создаётся, но возвращается “200”.
В чем именно проблема: Непонятно, был ли ресурс создан или обновлён.
Рекомендация: Возвращать “201 Created” + “Location” заголовок.
В чем именно улучшение: Чёткий сигнал, что ресурс успешно создан.
Пример запроса:
POST /users HTTP/1. 1
Content-Type: application/json
{
"name": "John",
"email": "john@mail.com"
}
Спорный момент: нет
5. “200 OK” при ошибках
Проблема: Даже при логических ошибках возвращается “200 OK”.
Пример из жизни: Запрос на несуществующего пользователя возвращает “{“error”: “User not found”}” с “200”.
В чем именно проблема: UI не отображает ошибку, так как статус говорит об успехе.
Рекомендация: Используйте “404 Not Found”, “400”, “401” и др.
В чем именно улучшение: Клиенты обрабатывают ошибки корректно.
Пример запроса:
GET /users/999999 HTTP/1. 1
Accept: application/json
Спорный момент: нет
6. Неиспользование “406 Not Acceptable”
Проблема: Игнорировать “Accept”-заголовок клиента.
Пример из жизни: Клиент запрашивает XML, а получает JSON, который не может распарсить.
В чем именно проблема: Нарушается контракт между клиентом и сервером.
Рекомендация: Возвращать “406 Not Acceptable”.
В чем именно улучшение: Клиент знает, что сервер не поддерживает нужный формат.
Пример запроса:
GET /products HTTP/1. 1
Accept: application/xml
Спорный момент:
Многие современные API не поддерживают множественные форматы, а всегда возвращают JSON.
Контекст:
- На практике клиенты просто не используют “Accept”, а сервер – не проверяет.
- Использование “406” оправдано, только если реально поддерживается “Content Negotiation”.
Вывод:
Необязательно проверять “Accept”, если API возвращает только один формат.
7. “400 Bad Request” вместо “415 Unsupported Media Type”
Проблема: Некорректный “Content-Type” обрабатывается как “400”.
Пример из жизни: Клиент отправляет “text/plain”, а сервер не может распарсить.
В чем именно проблема: Ошибка неявная, непонятно в чём дело.
Рекомендация: Возвращать “415 Unsupported Media Type”.
В чем именно улучшение: Указывает на реальную причину ошибки – неправильный формат данных.
Пример запроса:
POST /users HTTP/1. 1
Content-Type: text/plain
name=Ivan
Спорный момент: нет
8. Игнорирование “304 Not Modified”
Проблема: Сервер каждый раз возвращает полный ответ, даже если данные не менялись.
Пример из жизни: Клиент загружает профиль при каждом входе, даже если ничего не изменилось.
В чем именно проблема: Нагрузка на сеть и сервер.
Рекомендация: Используйте “ETag” и “If-None-Match”, возвращайте “304”.
В чем именно улучшение: Оптимизация трафика и производительности.
Пример запроса:
GET /profile HTTP/1. 1
If-None-Match: "abc123"
Спорный момент: нет
9. “404 Not Found” при неверном HTTP-методе
Проблема: Отправка метода, который не поддерживается – “404 Not Found”.
Пример из жизни: “DELETE” по адресу “/users” возвращает “404”, хотя “GET” по тому же адресу работает.
В чем именно проблема: Клиент думает, что ресурса нет.
Рекомендация: Использовать “405 Method Not Allowed” и “Allow”-заголовок.
В чем именно улучшение: Клиент получает корректную информацию о доступных методах.
Пример запроса:
DELETE /users HTTP/1. 1
Спорный момент: нет
10. “200 OK” при DELETE, даже если ничего не удалено
Проблема: Возвращается “200 OK”, даже если ресурс уже удалён.
Пример из жизни: Пользователь нажимает «удалить», получает “200”, но пользователь уже не существует.
В чем именно проблема: Вводит в заблуждение – кажется, что операция успешна.
Рекомендация: Возвращать “404 Not Found” или “204 No Content” в зависимости от результата.
В чем именно улучшение: UI получает достоверную информацию и может отреагировать корректно.
Пример запроса:
DELETE /users/12345 HTTP/1. 1
Спорный момент: нет
11. “201 Created” без “Location”-заголовка
Проблема: После создания ресурса возвращается “201 Created”, но без “Location”.
Пример из жизни: После создания пользователя клиент не знает, где он теперь доступен.
В чем именно проблема: Невозможно сразу перейти к новому ресурсу.
Рекомендация: Возвращать “Location: /users/{id}” в заголовке ответа.
В чем именно улучшение: Клиент может автоматически перейти к ресурсу или сохранить его путь.
Пример запроса:
POST /users HTTP/1. 1
Content-Type: application/json
{
"name": "John",
"email": "John@mail.com"
}
Спорный момент: нет
12. “500 Internal Server Error” при ошибке клиента
Проблема: Невалидный запрос приводит к “500”.
Пример из жизни: Отправка пустого запроса в поиск -> “500 Internal Server Error”.
В чем именно проблема: Клиент не понимает, что ошибка в запросе.
Рекомендация: Возвращать “400 Bad Request”.
В чем именно улучшение: Явное указание, что проблема на стороне клиента.
Пример запроса:
GET /search?q= HTTP/1. 1
Спорный момент:
Некоторые ошибки клиента могут привести к настоящей 500, если нет валидации на раннем этапе.
Контекст:
- Плохая реализация может упасть при “null” в JSON – это реальный баг.
- Но по смыслу это “400”.
Вывод:
Хорошо валидировать входные данные. Если сервер не выдержал плохой вход – это его вина, но ошибка всё равно от клиента.
13. “500 Internal Server Error” при недоступности базы данных
Проблема: В момент сбоя БД API возвращает “500”.
Пример из жизни: Во время деплоя Postgres не доступен – мониторинг сообщает, что API упал.
В чем именно проблема: Нельзя отличить баг от внешнего сбоя.
Рекомендация: Возвращать “503 Service Unavailable” и “Retry-After”.
В чем именно улучшение: Клиент знает, что сервис временно не работает и стоит подождать.
Пример запроса:
GET /users HTTP/1. 1
Спорный момент: нет
14. “200 OK” при сбое внешнего API
Проблема: Ответ сервера – “200 OK”, даже если внешний API не ответил.
Пример из жизни: API для оплаты не отвечает, но заказ всё равно отображается как «оплачен».
В чем именно проблема: Клиент получает ложную информацию.
Рекомендация: Возвращать “502 Bad Gateway” или “504 Gateway Timeout”.
В чем именно улучшение: Клиент видит, что проблема во внешнем компоненте, и может повторить позже.
Пример запроса:
POST /payment HTTP/1. 1
Content-Type: application/json
{
"orderId": "abc123",
"amount": 199.99
}
Спорный момент: нет
15. Ответ без заголовка “Content-Type”
Проблема: Сервер не указывает “Content-Type” в ответе.
Пример из жизни: Клиент получает ответ, не зная, в каком формате – JSON, XML или HTML.
В чем именно проблема: Невозможно безопасно парсить ответ.
Рекомендация: Всегда указывать “Content-Type” в ответах.
В чем именно улучшение: Клиенты могут безопасно обработать содержимое.
Пример запроса:
GET /products HTTP/1. 1
Спорный момент: нет
16. Неправильное использование “401 Unauthorized” для истёкшего токена
Проблема: Токен истёк, и сервер возвращает “401 Unauthorized”.
Пример из жизни: Приложение считает, что пользователь вообще не авторизован и принудительно выходит.
В чем именно проблема: Потеря пользовательского состояния.
Рекомендация: Возвращать “403 Forbidden” с сообщением об истечении токена.
В чем именно улучшение: Клиент может инициировать обновление токена, не разлогинивая пользователя.
Пример запроса:
GET /account HTTP/1. 1
Authorization: Bearer expired_token
Спорный момент:
Стандарты OAuth 2.0 и RFC6750 рекомендуют использовать “401” даже для истекших токенов.
Контекст:
- “401” означает: нужна повторная авторизация (в том числе токен протух).
- “403” – если токен действителен, но недостаточно прав.
Вывод:
“401” корректен даже при истечении токена, но в теле лучше явно указать причину (“token_expired”).
17. “200 OK” при ошибках в query-параметрах
Проблема: Параметр запроса некорректный, но сервер возвращает “200 OK” с пустым ответом.
Пример из жизни: Поиск по “?filter=unknown” возвращает пустой список, хотя фильтр не поддерживается.
В чем именно проблема: UI не понимает, что фильтр неправильный.
Рекомендация: Возвращать “400 Bad Request” или “422 Unprocessable Entity”.
В чем именно улучшение: Клиент может уведомить пользователя и предложить исправление.
Пример запроса:
GET /users?filter=unknown_field HTTP/1. 1
Спорный момент: нет
18. Игнорирование “ETag” в GET-запросах
Проблема: Сервер не использует “ETag”, всегда возвращает тело.
Пример из жизни: Страница профиля обновляется каждые 5 сек, даже если ничего не менялось.
В чем именно проблема: Избыточная передача данных.
Рекомендация: Использовать “ETag”, “If-None-Match”, и возвращать “304 Not Modified”.
В чем именно улучшение: Повышение производительности и экономия трафика.
Пример запроса:
GET /profile HTTP/1. 1
If-None-Match: "xyz789"
Спорный момент:
Многие API не реализуют кэширование через “ETag”, так как это сложно и не требуется для бизнес-данных.
Контекст:
- “ETag” особенно полезен при больших и часто запрашиваемых неизменяемых данных (например, CDN).
- В REST API можно обойтись ручным кэшированием на клиенте.
Вывод:
“ETag” не обязателен, но полезен для оптимизации; зависит от задач.
19. Неиспользование “202 Accepted” для асинхронных задач
Проблема: Операция выполняется в фоне, но сервер возвращает “200 OK”.
Пример из жизни: Генерация отчёта занимает 30 сек, но клиент получает “200” сразу и думает, что он готов.
В чем именно проблема: Пользователь не получает реального статуса выполнения.
Рекомендация: Возвращать “202 Accepted” и “Location” на ресурс статуса.
В чем именно улучшение: Клиент может отслеживать прогресс и понимать, что задача в процессе.
Пример запроса:
POST /reports HTTP/1. 1
Content-Type: application/json
{
"type": "monthly"
}
Спорный момент: нет
20. Ошибки в “OPTIONS”-запросах при CORS
Проблема: Сервер не обрабатывает “OPTIONS”-запросы корректно.
Пример из жизни: Браузер делает preflight-запрос, но получает “404”. Основной запрос блокируется.
В чем именно проблема: Кросс-доменные запросы не работают, UI ломается.
Рекомендация: Обрабатывать “OPTIONS” с нужными заголовками: “Access-Control-Allow-Origin”, “Access-Control-Allow-Methods”, и др.
В чем именно улучшение: Браузер получает разрешение и выполняет основной запрос.
Пример запроса:
OPTIONS /api/data HTTP/1. 1
Origin: https://client.app
Access-Control-Request-Method: POST
Спорный момент: нет
21. Использование “302 Found” после POST
Проблема: После успешного POST сервер делает редирект с “302 Found”.
Пример из жизни: После создания заказа браузер автоматически повторяет POST по редиректу – создаётся дубликат.
В чем именно проблема: Может привести к повторной отправке формы и созданию дубликатов.
Рекомендация: Используйте “303 See Other” для безопасного редиректа после POST.
В чем именно улучшение: Браузер делает “GET”, не повторяя POST.
Пример запроса:
POST /checkout HTTP/1. 1
Content-Type: application/json
{
"cartId": "a1b2c3"
}
Спорный момент: нет
22. “500 Internal Server Error” при ошибке CORS
Проблема: При нарушении CORS возвращается “500”.
Пример из жизни: Запрос с “Origin: otherdomain.com” блокируется, но клиент получает “500” и считает, что сервер сломан.
В чем именно проблема: Это не внутренняя ошибка сервера.
Рекомендация: Возвращать “403 Forbidden” с пояснением, что CORS-политика нарушена.
В чем именно улучшение: Клиент понимает, что ему не разрешён доступ.
Пример запроса:
GET /api/private HTTP/1. 1
Origin: http://untrusted-site.com
Спорный момент:
При ошибках CORS в браузере сервер вообще не доходит до запроса, и ответ не читается.
Контекст:
- CORS блокируется на уровне браузера, и даже “403” может не дойти до JS.
- Обычно достаточно правильно настроенных заголовков (“Access-Control-Allow-Origin” и др.).
Вывод:
“403” может быть теоретически верным, но не поможет, если CORS заблокирован на этапе preflight.
23. Отсутствие “Allow”-заголовка при “405 Method Not Allowed”
Проблема: Метод запрещён, но не указано, какие методы допустимы.
Пример из жизни: Клиент пытается сделать “PUT”, получает “405”, но не знает, что делать дальше.
В чем именно проблема: Нельзя программно адаптироваться к API.
Рекомендация: Возвращать “Allow: GET, POST”.
В чем именно улучшение: Клиент может скорректировать свой запрос.
Пример запроса:
PUT /users HTTP/1. 1
Спорный момент: нет
24. “403 Forbidden” вместо “401 Unauthorized”
Проблема: Отсутствие авторизации -> “403 Forbidden”.
Пример из жизни: Пользователь не залогинен, но UI получает “403” и не предлагает авторизацию.
В чем именно проблема: Клиент думает, что пользователь авторизован, но ему запрещено.
Рекомендация: Используйте “401 Unauthorized” с “WWW-Authenticate”.
В чем именно улучшение: Клиент предлагает пользователю залогиниться.
Пример запроса:
GET /private-data HTTP/1. 1
Спорный момент:
Многие путают эти статусы. В реальности:
“401” -> нет авторизации или она невалидна
“403” -> авторизация есть, но доступ запрещён
Контекст:
Часто в реальных системах “403” применяется как универсальный отказ, что может быть приемлемо для простоты.
Вывод:
Важно придерживаться разделения, особенно если у вас детализированная безопасность.
25. “400 Bad Request” при дублирующемся ресурсе (вместо “409 Conflict”)
Проблема: Повторная регистрация пользователя -> “400 Bad Request”.
Пример из жизни: Пользователь пытается создать аккаунт с тем же email, но UI не понимает, в чём дело.
В чем именно проблема: Ошибка не уточняет конфликт.
Рекомендация: Использовать “409 Conflict”.
В чем именно улучшение: Клиент может показать сообщение: “Пользователь с таким email уже существует”.
Пример запроса:
POST /users HTTP/1. 1
Content-Type: application/json
{
"email": "duplicate@example.com"
}
Спорный момент: нет
26. “200 OK” при неудачной аутентификации
Проблема: Неверный логин/пароль -> “200 OK”.
Пример из жизни: Форма логина не показывает ошибку, потому что не видит, что аутентификация не удалась.
В чем именно проблема: Нет сигнала о провале входа.
Рекомендация: Возвращать “401 Unauthorized”.
В чем именно улучшение: Клиент может показать корректное сообщение об ошибке входа.
Пример запроса:
POST /login HTTP/1. 1
Content-Type: application/json
{
"username": "user1",
"password": "wrongpass"
}
Спорный момент: нет
27. Неверный статус при загрузке файлов (“200 OK” вместо “201” или “202”)
Проблема: При загрузке файла сервер возвращает “200 OK”.
Пример из жизни: Пользователь думает, что файл уже обработан, хотя он только принят.
В чем именно проблема: Неправильное понимание состояния операции.
Рекомендация: Использовать “201 Created” или “202 Accepted”.
В чем именно улучшение: Клиент знает, что файл получен и/или ожидает обработки.
Пример запроса:
POST /upload HTTP/1. 1
Content-Type: multipart/form-data; boundary=-
-file-content-
Спорный момент:
Выбор зависит от бизнес-логики:
“201 Created”: если файл уже принят и размещён.
“202 Accepted”: если файл в процессе обработки.
Контекст:
Разные системы интерпретируют загрузку по-разному.
Вывод:
Оба варианта допустимы, но важно явно указать в теле статус обработки.
28. Отсутствие “Retry-After” при “503 Service Unavailable”
Проблема: Возвращается “503”, но клиент не знает, когда можно повторить.
Пример из жизни: Во время технических работ сервер отвечает “503”, клиент делает ретраи в цикле.
В чем именно проблема: Потенциальная перегрузка системы.
Рекомендация: Возвращать “Retry-After: 120”.
В чем именно улучшение: Клиент знает, когда повторить безопасно.
Пример запроса:
GET /api/dashboard HTTP/1. 1
Спорный момент: нет
29. Неиспользование “422 Unprocessable Entity” при ошибках валидации
Проблема: Некорректное значение поля -> “400 Bad Request”.
Пример из жизни: Email без “@” -> “400”, но клиент не знает, что именно не так.
В чем именно проблема: Не различаются синтаксические и валидационные ошибки.
Рекомендация: Возвращать “422 Unprocessable Entity”.
В чем именно улучшение: Чёткая диагностика ошибки: “Поле email должно быть валидным”.
Пример запроса:
POST /users HTTP/1. 1
Content-Type: application/json
{
"email": "invalid-email"
}
Спорный момент: нет
30. Игнорирование “429 Too Many Requests” при превышении лимита запросов
Проблема: Сервер возвращает “500”, когда пользователь превышает лимит.
Пример из жизни: Скрипт делает много запросов, сервер отвечает “500”, и скрипт продолжает, не понимая, в чём проблема.
В чем именно проблема: Увеличивает нагрузку и вызывает недоверие к стабильности.
Рекомендация: Использовать “429 Too Many Requests” с “Retry-After”.
В чем именно улучшение: Клиенту ясно, что нужно подождать перед следующей попыткой.
Пример запроса:
GET /api/search HTTP/1. 1
Спорный момент: нет
Почему 22 из 30 пунктов считаются обоснованными best practices
Они основаны на официальных спецификациях:
- RFC 7231 (HTTP/1. 1 Semantics and Content): описывает поведение статусов “200”, “201”, “202”, “204”, “400”, “403”, “404”, “405”, “406”, “409”, “415”, “500”, “501”, “503”.
- RFC 6585: определяет “429 Too Many Requests”.
- RFC 4918 (WebDAV): описывает “207 Multi-Status” и “422 Unprocessable Entity”.
- Все статусы, используемые в этих пунктах, официально задокументированы и предназначены именно для таких сценариев, как описано в статье.
Спорные пункты (сводная таблица)
Пункт | Описание | Комментарий |
---|---|---|
3 | 403 vs 404 | Зависит от уровня доверия (безопасность vs удобство) |
6 | 406 при Accept | Опционально, если есть поддержка разных форматов |
12 | 500 при ошибке клиента | Спорно, если ошибка не отловлена валидатором |
16 | 403 при истёкшем токене | Стандарты говорят: 401 с пояснением |
18 | ETag | Опционально, но полезно для оптимизации |
22 | 403 при CORS | Бесполезно, если ошибка на уровне браузера |
24 | 403 vs 401 | Часто путаются; важно чётко разграничить |
27 | 201 vs 202 при загрузке | Завит от синхронности операции |
“Строгость” рекомендаций (сводная таблица)
# | Ошибка | Строгость |
1 | “200 OK” при частичных ошибках | Context-Sensitive |
2 | `500` при удалённом API | RFC |
3 | Скрытие `403` ошибок за `404` | Context-Sensitive |
4 | `200 OK` вместо `201 Created` | RFC |
5 | `200 OK` при ошибках | RFC |
6 | Неиспользование `406` | Best Practice |
7 | `400` вместо `415` | RFC |
8 | Игнорирование `304` | Best Practice |
9 | `404` при неправильном методе | RFC |
10 | `200 OK` при DELETE ничего не удалено | RFC |
11 | `201 Created` без `Location` | Best Practice |
12 | `500` при ошибке клиента | Context-Sensitive |
13 | `500` при недоступности БД | RFC |
14 | `200 OK` при сбое внешнего API | RFC |
15 | Нет `Content-Type` в ответе | RFC |
16 | `403` при истекшем токене | Context-Sensitive |
17 | `200 OK` при ошибках query | Best Practice |
18 | Игнорирование `ETag` | Best Practice |
19 | Неиспользование `202 Accepted` | RFC |
20 | Ошибки в `OPTIONS` CORS | Best Practice |
21 | `302 Found` после POST | RFC |
22 | `500` при CORS | Context-Sensitive |
23 | Нет `Allow` при `405` | RFC |
24 | `403` вместо `401` | RFC |
25 | `400` вместо `409 Conflict` | RFC |
26 | `200 OK` при плохом логине | RFC |
27 | `200 OK` при загрузке файлов | Context-Sensitive |
28 | Нет `Retry-After` при `503` | RFC |
29 | Нет `422` при ошибках валидации | RFC (WebDAV/RFC4918) |
30 | Игнорирование `429 Too Many Requests` | RFC (RFC6585) |
Вывод
HTTP-статусы – это не просто числа в ответах сервера. Это язык, с помощью которого сервер общается с клиентом. И как в любом языке, значение слов имеет значение.
Мы рассмотрели 30 наиболее распространённых ошибок, которые регулярно встречаются в REST API – от некорректного использования “200 OK” до игнорирования “429 Too Many Requests” и “422 Unprocessable Entity”. Каждая из них на первый взгляд может показаться мелочью, но на практике:
- они увеличивают количество багов и непредсказуемого поведения,
- затрудняют сопровождение продукта,
- ломают UX и сбивают с толку разработчиков фронтенда и мобильных приложений,
- и, что важно, – увеличивают стоимость владения системой.
Хороший API – это тот, с которым удобно работать, который ведёт себя предсказуемо и честно сообщает клиенту, что происходит. Правильное использование HTTP-статусов – один из самых недооценённых, но мощных инструментов в этом направлении.
Если вы хотите:
- сократить количество костылей в коде клиентов,
- упростить логику обработки ошибок,
- улучшить документацию и дебаг,
- и сделать систему понятной как новым разработчикам, так и интеграторам –
то начните с исправления этих ошибок. Даже 3–5 корректно применённых статусов могут сделать API значительно удобнее.