🌐 Розділ 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, конфлікт унікального індексу або Optimistic Lock).
  • 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).

Пов’язані теми