Статус-коды — все 5 классов и ключевые коды
Статус-код — это первое, что смотрит ваш клиент в HTTP-ответе. Три цифры, и ваш код решает: радоваться, ругаться или ретраить. На бумаге кажется простым, на деле в разнице между 401 и 403, 502 и 504, 422 и 400 — годы опыта инженеров. В этом уроке разберём все коды, с которыми вы столкнётесь как Junior DE, и научимся правильно реагировать на каждый.
Структура: первая цифра определяет класс
Все статус-коды трёхзначные и делятся на 5 классов по первой цифре:
Простое правило: 4xx — твоя проблема, чини запрос. 5xx — их проблема, повтори позже. Это упрощение, но как первый ориентир работает хорошо.
1xx Informational — промежуточные
Эти коды — промежуточные ответы. Сервер говорит «я ещё не закончил, подожди». Junior встречает редко.
- 100 Continue — сервер готов принять тело запроса. Используется при больших uploads: клиент шлёт заголовки с
Expect: 100-continue, ждёт 100, потом шлёт тело. Если сервер не готов (например, тело слишком большое) — он ответит сразу 413, не приняв тело. - 101 Switching Protocols — сервер согласен переключиться на другой протокол. Используется при WebSocket-handshake (модуль 11): клиент шлёт
Upgrade: websocket, сервер отвечает 101 и переходит на WebSocket-протокол. - 102 Processing, 103 Early Hints — экзотика, в ETL вам не нужны.
2xx Success — всё хорошо
Самый приятный класс. Запрос обработан успешно.
Для Junior DE главные:
- 200 — стандартный успех на GET и большинство POST.
- 201 — создание (стандарт REST).
- 204 — успех без тела (чаще на DELETE).
- 202 — асинхронная обработка. Если получили — храните job ID и polling-те
/jobs/{id}.
# Пример 201 с Location:
curl -i -X POST https://httpbin.org/post -d '{"name":"test"}'
# HTTP/1.1 200 OK <- httpbin возвращает 200, в реальном API было бы 201
# Пример 204 -- DELETE без тела:
curl -i -X DELETE https://httpbin.org/delete
# HTTP/1.1 200 OK <- опять же httpbin не знает про 204
3xx Redirection — перенаправление
Сервер говорит «иди в другое место». Браузеры обычно следуют автоматически. HTTP-клиенты в коде — настраиваемо.
Тонкий момент: 301 и 302 vs 307 и 308. Исторически многие клиенты при 301/302 в ответ на POST меняли метод на GET (это не по спеке, но так делали). 307 и 308 (HTTP/1.1, RFC 7538) добавлены, чтобы явно требовать сохранения метода. На практике:
- GET + 301/302 — клиент идёт по новому URL с GET. Без проблем.
- POST + 301/302 — поведение зависит от клиента! Curl, requests, httpx по умолчанию меняют POST на GET (согласно историческому поведению). Это может быть неожиданно.
- POST + 307/308 — все клиенты повторяют POST.
# Посмотреть редирект, не следуя:
curl -I https://www.github.com
# HTTP/1.1 301 Moved Permanently
# Location: https://github.com/
# Следовать редиректам:
curl -L https://www.github.com
# Редиректит до конечного URL и возвращает финальный ответ
304 Not Modified — отдельная история. Сервер отвечает им только на conditional GET — когда клиент послал If-Modified-Since или If-None-Match с ETag. Если ресурс не изменился, сервер не шлёт тело — экономия трафика. Подробно про кэширование — модуль 3.
В Python requests по умолчанию следует редиректам (allow_redirects=True). Это удобно, но опасно: если API делает POST -> 302 -> другой POST, requests может сделать GET вместо POST на втором шаге. Если API критичен, лучше явно ставить allow_redirects=False и обрабатывать редиректы руками.
4xx Client Error — твоя проблема
Самый интересный класс. Сервер говорит: «то, что ты прислал, не валидно». Здесь Junior встречает большинство загадочных ситуаций.
401 vs 403 — ключевая разница
Эта пара путает Junior чаще всего. Запомните:
- 401 Unauthorized = «Я не знаю, кто ты». Нет токена или токен невалидный/истёк. Решение клиента: обновить токен или залогиниться.
- 403 Forbidden = «Я знаю, кто ты, но тебе сюда нельзя». Токен валидный, но недостаточно прав. Решение клиента: жаловаться админу, новый токен не поможет.
Если на ваш Authorization: Bearer xyz API стабильно отвечает 403 — не пытайтесь обновлять токен, это не поможет. У этой роли нет доступа к этому endpoint.
# 401: невалидный токен
curl -i -H 'Authorization: Bearer invalid_token' https://api.github.com/user
# HTTP/2 401
# {"message":"Bad credentials","documentation_url":"..."}
# 403: валидный токен, но нет прав на конкретный ресурс (например, private repo чужого юзера)
# 403 от GitHub также используется для rate-limit без токена -- это неклассически, но так делают
422 vs 400 — структурная vs семантическая невалидность
- 400 Bad Request — запрос невозможно даже распарсить. Битый JSON, неправильный Content-Type, отсутствует обязательное поле в headers.
- 422 Unprocessable Entity — JSON валидный, поля есть, но значения семантически неверные.
age=-5, email без@, дата в прошлом, когда ожидается будущее.
Стандарт REST: используйте 422 для валидации полей. На практике многие API возвращают 400 для всех валидационных ошибок — тоже допустимо. FastAPI/Pydantic возвращают 422 по умолчанию.
429 Too Many Requests — главный враг ETL
Retries и rate limits: tenacity на практике429 — самый болезненный код для Junior DE. Почти каждый production API имеет rate limits, и почти каждый ETL-скрипт рано или поздно в них упирается.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1715692800
{"message":"API rate limit exceeded"}
Что делать: прочитать Retry-After и подождать. Заголовок может быть в двух форматах:
- Секунды:
Retry-After: 60— подожди 60 секунд. - HTTP-date:
Retry-After: Wed, 21 Oct 2025 07:28:00 GMT— подожди до этого момента.
Ни в коем случае не «ретраить сразу же» — это типичный антипаттерн, который ведёт к ban’у IP. Подробно про rate limits и стратегии — модуль 9.
Если ETL получает 429 без Retry-After — это плохой API. Используйте exponential backoff: 1с, 2с, 4с, 8с, 16с, потом отказывайтесь. Не превращайтесь в DDoS-бот.
5xx Server Error — их проблема
Зачем нужен load balancer: scalability, redundancy, health checksСервер сломался. Запрос валидный, но обработать не получилось.
Когда какой код?
- 500 — есть, но мало информации. Что-то упало внутри. Иногда возвращают, когда не знают, какой код использовать. Нужно смотреть
X-Request-Id(если есть) и идти к owner API за помощью. - 502 — балансировщик/CDN не может связаться с реальным сервером. Backend упал, сеть моргнула. Обычно временно.
- 503 — сервер сам сказал «я перегружен». Должен быть Retry-After. Можно ждать и retry.
- 504 — таймаут на проксе. Backend думает дольше, чем балансировщик готов ждать. Часто признак того, что ваш запрос слишком тяжёлый — попробуйте уменьшить limit или page size.
Стратегия retry для 5xx
5xx обычно ретраиться имеет смысл — ошибка временная. Но:
- GET, HEAD, PUT, DELETE — безопасно ретраить (idempotent).
- POST, PATCH — ретраить только с Idempotency-Key, иначе дубликаты.
- Exponential backoff — 1с, 2с, 4с, 8с, 16с. Не больше 5 попыток. Подробнее — модуль 9.
import requests
import time
def get_with_retry(url, max_attempts=5):
for attempt in range(max_attempts):
try:
response = requests.get(url, timeout=10)
if response.status_code in (500, 502, 503, 504):
wait = 2 ** attempt
print(f"Got {response.status_code}, waiting {wait}s")
time.sleep(wait)
continue
return response
except requests.Timeout:
wait = 2 ** attempt
time.sleep(wait)
raise Exception(f"Failed after {max_attempts} attempts")
Стратегия обработки кодов в коде
Как Junior должен думать про статус-коды? Простая ментальная модель:
В Python с requests/httpx есть удобный метод response.raise_for_status() — кидает исключение для 4xx и 5xx. Используйте его, когда хотите fail-fast:
response = requests.get('https://api.github.com/users/torvalds')
response.raise_for_status() # кинет HTTPError для 4xx/5xx
data = response.json()
Попробуй сам
Поэкспериментируйте с кодами на httpbin:
# Получить любой статус-код:
curl -i https://httpbin.org/status/200
curl -i https://httpbin.org/status/404
curl -i https://httpbin.org/status/500
curl -i https://httpbin.org/status/429
# 401 без аутентификации:
curl -i https://httpbin.org/basic-auth/user/passwd
# HTTP/1.1 401 Unauthorized
# 401 с правильным паролем -> 200:
curl -i -u user:passwd https://httpbin.org/basic-auth/user/passwd
# Симулировать редирект:
curl -i https://httpbin.org/redirect/3
# Без -L: 302 Found, Location: /redirect/2
curl -L https://httpbin.org/redirect/3
# С -L: пройдёт все 3 редиректа
# Симулировать задержку (timeout):
curl -m 2 https://httpbin.org/delay/5
# Закроет соединение через 2 секунды (-m 2)
Поиграйтесь, посмотрите, как разные клиенты реагируют. Через 15 минут такой практики вы будете чувствовать статус-коды интуитивно.