HTTP статусы
HTTP статусы — трёхзначные коды ответа сервера, сообщающие клиенту результат запроса. Классы: 2xx (успех), 4xx (ошибка клиента), 5xx (ошибка сервера). 200 OK (GET, PUT, PATCH), 201 Created (POST, с заголовком Location), 204 No Content (DELETE, успех без тела), 202 Accepted (асинхронная операция). 400 Bad Request (невалидный JSON, отсутствие полей), 401 Unauthorized (нет токена или истёк), 403 Forbidden (нет прав), 404 Not Found, 409 Conflict (дубликат, конфликт версий), 422 Unprocessable Entity (ошибка бизнес-валидации), 429 Too Many Requests (rate limiting). 500 Internal Server Error (непойманное исключение), 503 Service Unavailable. Типичные ошибки: всегда возвращать 200 с ошибкой в теле, 401 вместо 403, 500 для ошибок валидации клиента, раскрытие деталей (стектрейс) в 5xx.
Введение: Язык ответов сервера
Представьте, что вы звоните в службу поддержки. Вы слышите голос: “Ваш звонок принят”, “Соединяю с оператором”, “Оператор занят, пожалуйста, ждите”, “Номер не существует”. Каждый ответ несёт определённую информацию о состоянии вашего запроса.
В мире HTTP сервер тоже говорит с клиентом на понятном языке. Этот язык — HTTP статус-коды. Трёхзначные числа, которые сообщают клиенту: “Всё хорошо”, “Ты ошибся”, “Я ошибся”, “Ресурс не найден” и так далее.
HTTP статус-коды — это стандартизированные коды ответа сервера, которые сообщают клиенту о результате обработки запроса. Они группируются по диапазонам, где первая цифра указывает на класс ответа.
Правильное использование статус-кодов делает API понятным и предсказуемым. Клиент может реагировать на разные статусы по-разному: повторить запрос при 503, показать “Не найдено” при 404, обработать ошибку валидации при 422.
Классы статус-кодов
| Диапазон | Класс | Значение | Примеры |
|---|---|---|---|
| 1xx | Informational | Информационные (промежуточные ответы) | 100 Continue, 101 Switching Protocols |
| 2xx | Success | Успех. Запрос обработан успешно | 200 OK, 201 Created, 204 No Content |
| 3xx | Redirection | Перенаправление. Нужен дополнительный запрос | 301 Moved Permanently, 304 Not Modified |
| 4xx | Client Error | Ошибка клиента. Запрос составлен неправильно или неавторизован | 400 Bad Request, 401 Unauthorized, 404 Not Found |
| 5xx | Server Error | Ошибка сервера. Сервер не смог обработать запрос | 500 Internal Server Error, 503 Service Unavailable |
2xx: Успех
200 OK
Самый распространённый статус. Всё хорошо, запрос выполнен, ответ в теле сообщения.
GET /users/123HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Иван"
}Когда использовать: GET, PUT, PATCH, POST (иногда).
201 Created
Ресурс успешно создан. Ответ должен содержать заголовок Location с URI нового ресурса.
POST /users
Content-Type: application/json
{"name": "Иван"}HTTP/1.1 201 Created
Location: /users/123
Content-Type: application/json
{
"id": 123,
"name": "Иван"
}Когда использовать: POST, PUT (если ресурс создан).
202 Accepted
Запрос принят в обработку, но ещё не завершён. Ответ не содержит конечного результата. Подходит для асинхронных операций.
POST /reports
{"date": "2024-01-01"}HTTP/1.1 202 Accepted
Location: /tasks/abc123
Content-Type: application/json
{
"task_id": "abc123",
"status": "processing",
"estimated_time": 30
}Когда использовать: Асинхронная обработка, очереди, длительные операции.
204 No Content
Запрос выполнен успешно, но ответ не имеет тела. Обычно для операций, которые не возвращают данных.
DELETE /users/123HTTP/1.1 204 No ContentPATCH /users/123
{"status": "active"}HTTP/1.1 204 No ContentКогда использовать: DELETE, POST (для операций без возврата), PUT/PATCH (если не нужно возвращать обновлённый ресурс).
3xx: Перенаправление
301 Moved Permanently
Ресурс навсегда перемещён на новый URI. Клиент должен использовать новый URI в будущем.
GET /old-users/123HTTP/1.1 301 Moved Permanently
Location: /users/123Когда использовать: Реорганизация API, переход на новые версии.
302 Found (Temporary Redirect)
Ресурс временно находится по другому URI. Клиент продолжает использовать старый URI.
GET /users/123HTTP/1.1 302 Found
Location: /users/123?version=2Когда использовать: Временное обслуживание, A/B тестирование.
304 Not Modified
Ресурс не изменился с момента последнего запроса. Клиент может использовать кешированную версию. Тело ответа пустое.
GET /users/123
If-None-Match: "abc123"HTTP/1.1 304 Not Modified
ETag: "abc123"Когда использовать: В сочетании с ETag или Last-Modified для эффективного кеширования.
4xx: Ошибка клиента
400 Bad Request
Запрос составлен неправильно. Сервер не может его обработать.
POST /users
Content-Type: application/json
{"name": null} # name не может быть nullHTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid request",
"details": "name cannot be null"
}Когда использовать: Невалидный JSON, неправильный формат даты, отсутствие обязательных полей.
401 Unauthorized
Клиент не аутентифицирован. Нужно предоставить учётные данные.
GET /users/123HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
{
"error": "Authentication required"
}Когда использовать: Отсутствует токен, истёк токен, неправильный API ключ.
403 Forbidden
Клиент аутентифицирован, но у него нет прав на выполнение операции.
GET /admin/users
Authorization: Bearer user_tokenHTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Insufficient permissions",
"required_role": "admin"
}Когда использовать: Пользователь не админ, попытка доступа к чужому ресурсу.
404 Not Found
Ресурс не найден.
GET /users/99999HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "User not found",
"id": 99999
}Когда использовать: Несуществующий ID, неправильный URI.
405 Method Not Allowed
HTTP метод не поддерживается для этого ресурса.
DELETE /users/123
# если DELETE не разрешёнHTTP/1.1 405 Method Not Allowed
Allow: GET, PUT, PATCHКогда использовать: POST на ресурсе, где разрешён только GET.
409 Conflict
Конфликт с текущим состоянием ресурса.
POST /users
{"email": "ivan@example.com"}
# email уже существуетHTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "Email already exists",
"email": "ivan@example.com"
}Когда использовать: Дубликат уникального поля, конфликт версий (optimistic locking).
422 Unprocessable Entity
Синтаксис запроса правильный, но семантически он некорректен.
POST /orders
{"amount": -100}HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "Validation failed",
"details": {
"amount": "must be greater than 0"
}
}Когда использовать: Валидация бизнес-правил, проверка данных.
429 Too Many Requests
Клиент превысил лимит запросов (rate limiting).
GET /users
# 1001-й запрос за минутуHTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "Rate limit exceeded",
"limit": 1000,
"reset": 60
}Когда использовать: При ограничении частоты запросов.
5xx: Ошибка сервера
500 Internal Server Error
Стандартная ошибка сервера. Что-то пошло не так, но сервер не может уточнить.
GET /users/123HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Something went wrong",
"request_id": "req_abc123"
}Когда использовать: Непойманное исключение, ошибка базы данных, сбой в коде.
Важно: Не возвращать детали ошибки (стектрейс) в production — это безопасность и информативность.
501 Not Implemented
Метод или функциональность не реализованы на сервере.
PATCH /users/123
# если PATCH не реализованHTTP/1.1 501 Not ImplementedКогда использовать: Функциональность в плане, но ещё не сделана.
503 Service Unavailable
Сервис временно недоступен (перегрузка, технические работы).
GET /users/123HTTP/1.1 503 Service Unavailable
Retry-After: 3600
Content-Type: application/json
{
"error": "Service is under maintenance",
"estimated_ready": "2024-01-15T12:00:00Z"
}Когда использовать: Плановые работы, перегрузка, деградация сервиса.
Какой статус выбрать
Успешные операции
| Операция | Статус | Тело | Заголовок Location |
|---|---|---|---|
| GET (один ресурс) | 200 | Ресурс | - |
| GET (коллекция) | 200 | Список ресурсов | - |
| POST (создание) | 201 | Созданный ресурс | URI нового ресурса |
| PUT (обновление) | 200 | Обновлённый ресурс | - |
| PATCH (обновление) | 200 | Обновлённый ресурс | - |
| DELETE (удаление) | 204 | (пусто) | - |
| Асинхронная операция | 202 | Информация о задаче | URI задачи |
Ошибки клиента
| Ситуация | Статус |
|---|---|
| Невалидный JSON | 400 |
| Отсутствует обязательное поле | 400 |
| Неверный формат даты | 400 |
| Нет токена авторизации | 401 |
| Истёк токен | 401 |
| Нет прав на операцию | 403 |
| Ресурс не найден | 404 |
| Метод не поддерживается | 405 |
| Дубликат уникального поля | 409 |
| Бизнес-валидация не пройдена | 422 |
| Превышен лимит запросов | 429 |
Ошибки сервера
| Ситуация | Статус |
|---|---|
| Непойманное исключение | 500 |
| Ошибка подключения к БД | 500 |
| Функциональность не реализована | 501 |
| Сервис временно недоступен | 503 |
Кастомные статусы
Не придумывайте свои статусы. Используйте стандартные. Стандартных достаточно.
Плохо:
HTTP/1.1 420 Enhance Your Calm
HTTP/1.1 450 Blocked by Parental ControlsХорошо: Используйте существующие или расширяйте тело ответа дополнительной информацией.
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "VALIDATION_FAILED",
"code": "CUSTOM_001",
"message": "Email already exists"
}Статусы в теле ответа
Некоторые API (особенно не RESTful) возвращают статус в теле:
HTTP/1.1 200 OK
{"status": "error", "code": 404, "message": "User not found"}Почему это плохо:
- Клиент должен парсить тело, чтобы понять статус
- Стандартные инструменты (браузер, прокси) не поймут такой ответ
- HTTP кеширование работает по статус-кодам, не по телу
Лучше: Использовать правильный HTTP статус.
HTTP/1.1 404 Not Found
{"error": "User not found"}Статусы и повтор запросов
Некоторые ошибки можно исправить повторным запросом.
| Статус | Можно ли повторить | Примечание |
|---|---|---|
| 408 Request Timeout | Да | Клиент может повторить тот же запрос |
| 429 Too Many Requests | Да, после паузы | Нужно подождать Retry-After |
| 500 Internal Server Error | Осторожно | Если ошибка временная, можно повторить |
| 502 Bad Gateway | Осторожно | Временная проблема с прокси |
| 503 Service Unavailable | Да, после паузы | Retry-After указывает, как долго ждать |
| 504 Gateway Timeout | Осторожно | Таймаут на шлюзе |
Идемпотентность важна: GET, PUT, DELETE можно безопасно повторять. POST — не всегда.
Практические примеры
Пример 1: Создание пользователя
POST /users
{"name": "Иван", "email": "ivan@example.com"}Успех:
HTTP/1.1 201 Created
Location: /users/123
{"id": 123, "name": "Иван", "email": "ivan@example.com"}Email уже существует:
HTTP/1.1 409 Conflict
{"error": "Email already exists", "email": "ivan@example.com"}Невалидные данные:
HTTP/1.1 422 Unprocessable Entity
{
"error": "Validation failed",
"details": {
"name": "cannot be empty",
"email": "invalid format"
}
}Пример 2: Получение пользователя
GET /users/123Успех:
HTTP/1.1 200 OK
{"id": 123, "name": "Иван"}Не найден:
HTTP/1.1 404 Not Found
{"error": "User not found", "id": 123}Нет токена:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
{"error": "Authentication required"}Пример 3: Обновление пользователя
PATCH /users/123
{"phone": "+7-999-123-45-67"}Успех (с возвратом):
HTTP/1.1 200 OK
{"id": 123, "name": "Иван", "phone": "+7-999-123-45-67"}Успех (без возврата):
HTTP/1.1 204 No ContentКонфликт версий (optimistic locking):
HTTP/1.1 409 Conflict
{
"error": "Resource was modified by another request",
"current_version": 3,
"your_version": 2
}Частые ошибки
Ошибка 1: Всегда возвращать 200 OK
HTTP/1.1 200 OK
{"error": "User not found"}Почему плохо: Клиент должен парсить тело, чтобы понять, ошибка это или успех.
Как исправить: 404 Not Found.
Ошибка 2: 401 вместо 403
GET /admin/users
Authorization: Bearer user_token (пользователь не админ)
→ 401 UnauthorizedПочему плохо: 401 означает “ты не представился”. 403 — “ты представился, но тебе нельзя”.
Как исправить: 403 Forbidden.
Ошибка 3: 500 для ошибок валидации
POST /users
{"email": "invalid"}
→ 500 Internal Server ErrorПочему плохо: Ошибка клиента, не сервера.
Как исправить: 400 Bad Request или 422 Unprocessable Entity.
Ошибка 4: 404 для ресурса, который никогда не существовал
Для коллекции ресурсов 404 иногда возвращают, если коллекция пуста:
GET /users?status=deleted
→ 404 Not FoundПочему плохо: Пустая коллекция — это не “не найдено”. Это валидный ответ.
Как исправить: 200 OK с пустым массивом.
Ошибка 5: Раскрытие деталей ошибки в 500
HTTP/1.1 500 Internal Server Error
{"error": "SQLSTATE[42S02]: Base table or view not found"}Почему плохо: Информация о структуре базы данных — угроза безопасности.
Как исправить: Общее сообщение, request_id для внутреннего отслеживания.
Резюме для системного аналитика
HTTP статус-коды — это язык ответов сервера. Клиент по коду понимает, что произошло, не парся тело ответа. 200 — хорошо, 404 — не найдено, 500 — ошибка сервера.
2xx — успех. 200 OK (всё хорошо), 201 Created (ресурс создан), 204 No Content (успех без тела), 202 Accepted (асинхронная операция).
4xx — ошибка клиента. Клиент что-то сделал не так. 400 (плохой запрос), 401 (не авторизован), 403 (нет прав), 404 (не найдено), 409 (конфликт), 422 (ошибка валидации), 429 (слишком много запросов).
5xx — ошибка сервера. Сервер не смог обработать запрос. 500 (внутренняя ошибка), 501 (не реализовано), 503 (сервис недоступен).
Никогда не возвращайте 200 с ошибкой в теле. Используйте правильный статус-код. Это делает API предсказуемым и совместимым со стандартными инструментами.
Не раскрывайте детали ошибок в 5xx. Стектейс, пути к файлам, структура БД — угроза безопасности. Логируйте детали внутри, клиенту — общее сообщение и request_id.