🌐 Раздел 6 · Вопрос #9

Какие HTTP статус коды вы знаете

Первая цифра определяет класс ответа:


🟢 Junior Level

HTTP статус-код (Status Code) — это трехзначное числовое значение, которое сервер отправляет в стартовой строке ответа клиенту для сообщения о результате обработки запроса.

Первая цифра определяет класс ответа:

Класс Категория Значение
1xx Информационные (Informational) Запрос получен, обработка продолжается (напр. 100 Continue).
2xx Успех (Success) Действие успешно принято, понято и обработано.
3xx Перенаправление (Redirection) Для завершения запроса требуются дальнейшие действия клиента.
4xx Ошибка клиента (Client Error) Запрос содержит синтаксическую ошибку или не может быть выполнен по вине клиента.
5xx Ошибка сервера (Server Error) Сервер столкнулся с непредвиденной ошибкой при обработке корректного запроса.

Базовые статус-коды, которые обязан знать каждый разработчик

  • 200 OK: Стандартный ответ при успешном выполнении запроса (обычно с телом ответа).
  • 201 Created: Ресурс успешно создан (например, в ответ на POST). Сервер обычно возвращает заголовок Location с URI нового ресурса.
  • 204 No Content: Запрос выполнен успешно, но тело ответа намеренно отсутствует (типично для DELETE или PUT).
  • 301 Moved Permanently: Ресурс навсегда перемещен на новый URL.
  • 302 Found: Временное перенаправление на другой URL.
  • 400 Bad Request: Некорректный синтаксис запроса (битый JSON, недопустимый формат данных).
  • 401 Unauthorized: Пользователь не аутентифицирован (сервер не знает, кто обращается; требуется заголовок Authorization).
  • 403 Forbidden: Пользователь аутентифицирован, но у него нет прав доступа к ресурсу.
  • 404 Not Found: Запрошенный ресурс не найден на сервере.
  • 500 Internal Server Error: Непредвиденное исключение или сбой в коде приложения на стороне сервера.
  • 503 Service Unavailable: Сервер временно не может обработать запрос (перегрузка, техническое обслуживание).

🟡 Middle Level

Разделение кодов и протокольные тонкости (RFC 9110)

1. Успешные ответы (2xx)

  • 202 Accepted: Запрос принят на обработку, но она еще не завершена. Применяется в асинхронных архитектурах (очереди, фоновые задачи). Клиент получает идентификатор задачи для последующей проверки статуса.
  • 206 Partial Content: Сервер возвращает только часть содержимого ресурса (используется при докачке файлов и видео-стриминге по заголовку Range).

2. Перенаправления (3xx): Революция 307 и 308

Исторически браузеры ошибочно трансформировали метод POST в GET при получении редиректов 301 и 302. Для устранения этой проблемы в спецификацию были введены строгие коды:

  • 303 See Other: Прямо предписывает клиенту сделать GET на новый URL (основа паттерна Post/Redirect/Get).
  • 307 Temporary Redirect: Временное перенаправление, строго сохраняющее исходный HTTP-метод и тело (POST остается POST).
  • 308 Permanent Redirect: Постоянное перенаправление, строго сохраняющее HTTP-метод и тело.
  • 304 Not Modified: Ответ на условный запрос (If-None-Match или If-Modified-Since). Тело ответа отсутствует, клиент берет данные из локального кэша (экономия трафика и времени).

3. Ошибки клиента (4xx)

  • 401 vs 403:
    • 401 Unauthorized обязан содержать заголовок WWW-Authenticate. Означает: «Вы не подтвердили свою личность».
    • 403 Forbidden: личность подтверждена, но RBAC/ABAC доступ закрыт. Повтор запроса с теми же учетными данными бесполезен.
  • 400 vs 422:
    • 400 Bad Request: сервер не смог распарсить запрос (синтаксический сбой, невалидный JSON, некорректная кодировка).
    • 422 Unprocessable Content (ранее Entity): синтаксис JSON корректен, но нарушены бизнес-правила или Bean Validation (например, отрицательный возраст или невалидный email).
  • 409 Conflict: Конфликт состояния данных (попытка регистрации уже существующего email, конфликт уникального индекса).
  • 410 Gone: Ресурс был на сервере, но намеренно и навсегда удален без предоставления адреса перенаправления.
  • 412 Precondition Failed: Не прошла проверка условного заголовка (If-Match при оптимистической блокировке).
  • 415 Unsupported Media Type: Сервер не поддерживает формат данных, переданный в Content-Type (например, прислан XML вместо JSON).
  • 429 Too Many Requests: Сработал Rate Limiter. Сервер обязан передать заголовок Retry-After: <seconds>.

4. Ошибки сервера (5xx)

  • 502 Bad Gateway: Прокси-сервер (NGINX / Envoy / Cloudflare) получил некорректный ответ или сброс TCP-соединения от бэкенда (Java-приложения).
  • 503 Service Unavailable: Бэкенд временно недоступен (перегрузка пула потоков Tomcat, сервис на обслуживании).
  • 504 Gateway Timeout: Бэкенд не ответил прокси-серверу за отведенное время (таймаут proxy_read_timeout).

🔴 Senior Level

Стандартизация ошибок: RFC 9457 (Problem Details for HTTP APIs)

Возврат произвольных JSON-структур при ошибках ({"error": "bad request", "code": 12}) признан анти-паттерном. Современным стандартом индустрии является RFC 9457 (заменивший RFC 7807), нативно встроенный в Spring Boot 3+ через класс ProblemDetail (Content-Type: application/problem+json).

{
  "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient Funds",
  "status": 422,
  "detail": "Your balance of 1500 USD is insufficient for debiting 2000 USD",
  "instance": "/api/v1/accounts/101/debit",
  "timestamp": "2026-09-26T08:30:00Z",
  "traceId": "9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d"
}

Production-ready обработчик ошибок на Spring Boot 3

package com.example.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.net.URI;
import java.time.Instant;
import java.util.Map;
import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 1. Ошибки валидации DTO -> 422 Unprocessable Content
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ProblemDetail handleValidationException(MethodArgumentNotValidException ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
                HttpStatus.UNPROCESSABLE_ENTITY, 
                "Validation failed for one or more fields"
        );
        problem.setTitle("Unprocessable Content");
        problem.setType(URI.create("https://api.example.com/errors/validation"));
        problem.setProperty("timestamp", Instant.now());

        Map<String, String> invalidFields = ex.getBindingResult().getFieldErrors().stream()
                .collect(Collectors.toMap(
                        FieldError::getField,
                        fe -> fe.getDefaultMessage() != null ? fe.getDefaultMessage() : "Invalid value",
                        (existing, replacement) -> existing
                ));

        problem.setProperty("invalidFields", invalidFields);
        return problem;
    }

    // 2. Бизнес-конфликт -> 409 Conflict
    @ExceptionHandler(UserAlreadyExistsException.class)
    public ProblemDetail handleUserExistsException(UserAlreadyExistsException ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
                HttpStatus.CONFLICT, 
                ex.getMessage()
        );
        problem.setTitle("Resource Conflict");
        problem.setType(URI.create("https://api.example.com/errors/user-conflict"));
        problem.setProperty("timestamp", Instant.now());
        return problem;
    }

    // 3. Непредвиденные ошибки -> 500 Internal Server Error (без утечки stack trace!)
    @ExceptionHandler(Exception.class)
    public ProblemDetail handleGenericException(Exception ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
                HttpStatus.INTERNAL_SERVER_ERROR,
                "An unexpected internal error occurred. Please contact support."
        );
        problem.setTitle("Internal Server Error");
        problem.setType(URI.create("https://api.example.com/errors/internal"));
        problem.setProperty("timestamp", Instant.now());
        return problem;
    }
}

Highload, Observability и влияние кодов на инфраструктуру

  1. Negative Caching в CDN / Edge:
    • Ответы 404 Not Found могут кэшироваться в CDN (Cloudflare) на короткое время (например, 10 секунд), чтобы защитить бэкенд от сканирования ботами и DDoS.
  2. Circuit Breaker (Resilience4j / Envoy):
    • Circuit Breaker настраивается на процент ответов 5xx (включая 502, 503, 504). Ошибки 4xx классифицируются как штатные ошибки клиентов и не должны приводить к размыканию цепи.
  3. Observability (SRE Golden Signals):
    • Метрики http_server_requests_seconds_count{status=~"5.."} запускают PagerDuty алерты, так как 500 означает дефект в коде бэкенда, тогда как всплеск 429 указывает на необходимость пересмотра квот Rate Limiter.

🎯 Шпаргалка для интервью

30-секундный ответ

HTTP статус-коды делятся на 5 классов:

  • 1xx: Информационные (100 Continue).
  • 2xx: Успех: 200 OK, 201 Created (с Location), 202 Accepted (асинхронные задачи), 204 No Content (успех без тела).
  • 3xx: Редиректы: 301/302 (исторические), 303 See Other (PRG-паттерн), 304 Not Modified (кэш), 307/308 (строго сохраняют метод и тело).
  • 4xx: Ошибки клиента: 400 (синтаксис), 401 (нет аутентификации, нужен WWW-Authenticate), 403 (нет прав), 404 (не найдено), 409 (конфликт данных), 412 (конфликт ETag), 422 (ошибки валидации), 429 (Rate limit с Retry-After).
  • 5xx: Ошибки сервера: 500 (баг кода), 502 (упал бэкенд за NGINX), 503 (перегрузка сервиса), 504 (таймаут ответа бэкенда).

В современном REST API ошибки стандартизируются по RFC 9457 Problem Details.

4 каверзных вопроса с ответами

  1. В чем разница между статус-кодами 302 Found и 307 Temporary Redirect? Ответ: Исторически браузеры при получении кода 302 Found трансформировали метод POST в GET. Код 307 Temporary Redirect строго специфицирован в RFC 9110: клиент обязан повторить перенаправленный запрос с тем же самым HTTP-методом и телом, что критично для безопасности мутирующих операций в REST API.

  2. Когда следует возвращать 400 Bad Request, а когда 422 Unprocessable Content? Ответ: 400 Bad Request возвращается при синтаксических ошибках, когда сервер в принципе не смог распарсить запрос (битый JSON, недопустимый заголовок). 422 Unprocessable Content возвращается, когда синтаксис корректен, но нарушена семантика данных (ошибки валидации полей, пустые обязательные параметры).

  3. Почему код 500 не должен использоваться для бизнес-ошибок (например, недостаточно денег на счете)? Ответ: Статусы 5xx сигнализируют о сбое инфраструктуры или необработанном исключении в коде (баге). Инфраструктурные мониторинги (SRE, Prometheus, Circuit Breaker) реагируют на 5xx размыканием цепи и вызовом дежурных инженеров. Для ожидаемых отказов бизнес-логики необходимо возвращать 4xx (409 Conflict, 422 Unprocessable Content).

  4. Что обязательно должен содержать ответ сервера со статусом 401 Unauthorized по стандарту HTTP? Ответ: По спецификации RFC 9110 (раздел 15.5.2) ответ с кодом 401 Unauthorized обязан содержать заголовок ответа WWW-Authenticate, указывающий хотя бы одну поддерживаемую схему аутентификации (например, WWW-Authenticate: Bearer realm="api").

Красные флаги (чего категорически нельзя говорить)

  • ❌ «401 и 403 — это синонимы» (401 — неизвестна личность пользователя, 403 — личность известна, но доступ запрещен политикой прав).
  • ❌ «Если в БД нет записи по ID, контроллер должен падать с 500 ошибкой» (отсутствие данных по ID клиента — это штатная ситуация 404 Not Found).
  • ❌ «Для бизнес-ошибок всегда возвращаем 200 OK, а статус пишем внутри JSON» (антипаттерн SOAP/RPC, ломающий HTTP-кэширование, балансировку и метрики).
  • ❌ «Код 204 No Content возвращает пустую JSON-строку “{}”» (код 204 гарантирует физическое отсутствие тела ответа, размер Content-Length: 0).

Связанные темы