MidТеория4 min

Статус-коды HTTP

Все важные статус-коды 1xx-5xx, разница между 301/302, 401/403 и другие подвохи

Статус-коды — трёхзначные числа в ответе HTTP, определяющие результат запроса. На экзамене Symfony необходимо знать точное значение каждого кода, особенно разницу между похожими кодами.

Классы статус-кодов

Диапазон Класс Описание
1xx Informational Запрос принят, продолжается обработка
2xx Success Запрос успешно обработан
3xx Redirection Для завершения нужно дополнительное действие
4xx Client Error Ошибка на стороне клиента
5xx Server Error Ошибка на стороне сервера

1xx — Информационные

Код Имя Описание
100 Continue Сервер получил заголовки, клиент может отправить тело
101 Switching Protocols Переключение протокола (WebSocket)
103 Early Hints Preload ресурсов до финального ответа
<?php

declare(strict_types=1);

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\WebLink\Link;

// 103 Early Hints — preload resources before main response
class ProductController extends AbstractController
{
    public function show(int $id): Response
    {
        // Send 103 Early Hints for preloading CSS/JS
        $this->sendEarlyHints([
            new Link('preload', '/build/app.css', ['as' => 'style']),
            new Link('preload', '/build/app.js', ['as' => 'script']),
        ]);

        // Process the heavy request...
        $product = $this->repository->find($id);

        return $this->render('product/show.html.twig', [
            'product' => $product,
        ]);
    }
}

2xx — Успешные

Код Имя Когда использовать Symfony-константа
200 OK Успешный GET, PUT, PATCH Response::HTTP_OK
201 Created Успешный POST (ресурс создан) Response::HTTP_CREATED
202 Accepted Запрос принят, но ещё не обработан Response::HTTP_ACCEPTED
204 No Content Успешный DELETE (нет тела ответа) Response::HTTP_NO_CONTENT
206 Partial Content Частичная загрузка (Range) Response::HTTP_PARTIAL_CONTENT
<?php

declare(strict_types=1);

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

// 200 — standard success
return new JsonResponse(['data' => $items]);

// 201 — resource created (include Location header!)
return new JsonResponse(
    ['id' => $product->getId()],
    Response::HTTP_CREATED,
    ['Location' => '/api/products/' . $product->getId()]
);

// 202 — accepted for async processing
return new JsonResponse(
    ['status' => 'processing', 'job_id' => $jobId],
    Response::HTTP_ACCEPTED
);

// 204 — successful delete, no body
return new Response(null, Response::HTTP_NO_CONTENT);

Подвох экзамена: 201 Created должен содержать заголовок Location с URL нового ресурса. 204 No Content НЕ должен содержать тело ответа. Если на экзамене видите 201 без Location или 204 с телом — это неправильно.

3xx — Перенаправления

Код Имя Сохраняет метод Постоянный Использование
301 Moved Permanently Нет (→ GET) Да SEO-редирект
302 Found Нет (→ GET) Нет Временный редирект
303 See Other Нет (→ GET) Нет После POST (PRG)
307 Temporary Redirect Да Нет Временный, метод сохраняется
308 Permanent Redirect Да Да Постоянный, метод сохраняется
304 Not Modified — — Кэш валиден

Подвох экзамена: Главная разница между 301 и 308 — сохранение HTTP-метода. При 301 POST может стать GET (браузеры так делают). При 308 метод ВСЕГДА сохраняется. Аналогично 302 vs 307. Это критически важный вопрос на экзамене.

<?php

declare(strict_types=1);

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;

// 301 — permanent redirect (browser caches!)
return new RedirectResponse('/new-url', Response::HTTP_MOVED_PERMANENTLY);

// 302 — temporary redirect (default in Symfony)
return $this->redirect('/temporary-url'); // 302 by default

// 303 — redirect after POST (PRG pattern)
return $this->redirectToRoute('product_list', [], Response::HTTP_SEE_OTHER);

// 307 — temporary redirect preserving method
return new RedirectResponse('/api/v2/products', Response::HTTP_TEMPORARY_REDIRECT);

// 308 — permanent redirect preserving method
return new RedirectResponse('/api/v2/products', Response::HTTP_PERMANENTLY_REDIRECT);

304 Not Modified

<?php

declare(strict_types=1);

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class ArticleController extends AbstractController
{
    public function show(int $id, Request $request): Response
    {
        $article = $this->repository->find($id);
        $lastModified = $article->getUpdatedAt();

        $response = new Response();
        $response->setLastModified($lastModified);
        $response->setEtag(md5($article->getContent()));

        // If client cache is still valid → 304
        if ($response->isNotModified($request)) {
            return $response; // 304, no body
        }

        $response->setContent($this->renderView('article/show.html.twig', [
            'article' => $article,
        ]));

        return $response;
    }
}

4xx — Ошибки клиента

Код Имя Когда Symfony-константа
400 Bad Request Невалидный синтаксис запроса HTTP_BAD_REQUEST
401 Unauthorized Не аутентифицирован HTTP_UNAUTHORIZED
403 Forbidden Аутентифицирован, но нет доступа HTTP_FORBIDDEN
404 Not Found Ресурс не найден HTTP_NOT_FOUND
405 Method Not Allowed Метод не поддерживается HTTP_METHOD_NOT_ALLOWED
409 Conflict Конфликт (параллельное изменение) HTTP_CONFLICT
415 Unsupported Media Type Неподдерживаемый Content-Type HTTP_UNSUPPORTED_MEDIA_TYPE
422 Unprocessable Entity Валидация не прошла HTTP_UNPROCESSABLE_ENTITY
429 Too Many Requests Rate limiting HTTP_TOO_MANY_REQUESTS

Подвох экзамена: 401 Unauthorized на самом деле означает "Unauthenticated" — пользователь НЕ представился. 403 Forbidden — пользователь представился (authenticated), но у него НЕТ прав (not authorized). Название 401 вводит в заблуждение. Запомните: 401 = "кто ты?", 403 = "я знаю кто ты, но тебе нельзя".

400 vs 422

<?php

declare(strict_types=1);

// 400 Bad Request — request cannot be parsed
// (malformed JSON, missing required headers, invalid syntax)
if ($request->getContentTypeFormat() !== 'json') {
    throw new BadRequestHttpException('Content-Type must be application/json');
}

// 422 Unprocessable Entity — request is syntactically correct
// but semantically invalid (validation errors)
$violations = $validator->validate($dto);
if (count($violations) > 0) {
    throw new UnprocessableEntityHttpException('Validation failed');
}

405 Method Not Allowed

<?php

declare(strict_types=1);

// Symfony automatically returns 405 when route exists
// but requested method is not allowed
// Response MUST include Allow header

// GET /api/products → 200 (route exists for GET)
// DELETE /api/products → 405 (route exists, but only GET/POST allowed)
// Response: 405 Method Not Allowed
// Allow: GET, POST

Подвох экзамена: При 405 ответ ОБЯЗАН содержать заголовок Allow с перечислением допустимых методов. Symfony добавляет его автоматически. При 429 рекомендуется включать заголовок Retry-After.

5xx — Ошибки сервера

Код Имя Когда Symfony-константа
500 Internal Server Error Необработанное исключение HTTP_INTERNAL_SERVER_ERROR
502 Bad Gateway Прокси получил ошибку от upstream HTTP_BAD_GATEWAY
503 Service Unavailable Сервер временно недоступен HTTP_SERVICE_UNAVAILABLE
504 Gateway Timeout Прокси не дождался ответа upstream HTTP_GATEWAY_TIMEOUT
<?php

declare(strict_types=1);

use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;

// 503 with Retry-After header
throw new ServiceUnavailableHttpException(
    300, // Retry-After: 300 seconds
    'Database maintenance in progress'
);

Константы Symfony Response

<?php

declare(strict_types=1);

use Symfony\Component\HttpFoundation\Response;

// All status codes as class constants
Response::HTTP_OK;                     // 200
Response::HTTP_CREATED;                // 201
Response::HTTP_NO_CONTENT;             // 204
Response::HTTP_MOVED_PERMANENTLY;      // 301
Response::HTTP_FOUND;                  // 302
Response::HTTP_SEE_OTHER;              // 303
Response::HTTP_NOT_MODIFIED;           // 304
Response::HTTP_TEMPORARY_REDIRECT;     // 307
Response::HTTP_PERMANENTLY_REDIRECT;   // 308
Response::HTTP_BAD_REQUEST;            // 400
Response::HTTP_UNAUTHORIZED;           // 401
Response::HTTP_FORBIDDEN;              // 403
Response::HTTP_NOT_FOUND;              // 404
Response::HTTP_METHOD_NOT_ALLOWED;     // 405
Response::HTTP_CONFLICT;               // 409
Response::HTTP_UNPROCESSABLE_ENTITY;   // 422
Response::HTTP_TOO_MANY_REQUESTS;      // 429
Response::HTTP_INTERNAL_SERVER_ERROR;  // 500
Response::HTTP_BAD_GATEWAY;            // 502
Response::HTTP_SERVICE_UNAVAILABLE;    // 503

// Check if status code is successful
$response->isSuccessful();    // 2xx
$response->isRedirection();   // 3xx
$response->isClientError();   // 4xx
$response->isServerError();   // 5xx

Подвох экзамена: Symfony предоставляет метод Response::isInformational() для 1xx, isSuccessful() для 2xx и т.д. Метод isOk() проверяет ТОЛЬКО код 200, а не весь диапазон 2xx. Не путайте isOk() и isSuccessful().