HTTP методы и коды на собеседовании системного аналитика

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.

Зачем спрашивают

Системный аналитик пишет ТЗ на API. Если в ТЗ «POST /users/123/delete возвращает 200 с body {success: true}» — это четыре проблемы в одной строке: неверный метод, неверный код, неверная семантика, неверный контракт. На собесе такое разносят, не дочитав до конца.

Главная боль без понимания HTTP — клиентская команда (мобилка, фронт) делает retry на 5xx, бэкенд возвращает 5xx на бизнес-валидацию (нельзя купить, не хватает денег), retry дублирует попытку списать деньги. Это реальный продакшен-баг, и он начинается с неверных кодов в спецификации.

Эта статья закрывает базу: что выбирать в OpenAPI, как описывать в ТЗ, на чём не плыть на собесе.

Методы и их свойства

Метод Назначение Safe Idempotent Тело запроса
GET Чтение да да нет
HEAD Чтение метаданных (без body) да да нет
OPTIONS Поддерживаемые методы да да нет
POST Создание / нестандартное действие нет нет да
PUT Полная замена ресурса нет да да
PATCH Частичное обновление нет нет* да
DELETE Удаление нет да редко

*PATCH формально не идемпотентный (зависит от тела). Если тело {"status": "active"} — фактически идемпотентен. Если {"counter": "+1"} — нет.

Safe — не меняет состояние сервера. Можно кешировать, prefetch-ить.

Idempotent — повторный вызов с теми же параметрами даёт тот же эффект, что и один. Безопасно ретраить при сетевых ошибках.

На собесе классический вопрос: «Можно ли использовать POST для чтения?» Технически да (если параметров слишком много для query string). Семантически — нет, нарушает principle of least surprise. Кешироваться не будет, retry опасен.

Идемпотентность и safe методы

GET — идемпотентный по природе (только чтение). Ретраить безопасно.

PUT — идемпотентный, потому что заменяет ресурс полностью. PUT /users/123 с одним body — сколько раз ни вызови, конечное состояние одно.

DELETE — идемпотентный. DELETE /users/123 1 раз — удалил. Второй раз — уже удалён, но с точки зрения состояния системы — тот же результат. Возвращать 200/204 в первый раз и 404 во второй — нормально.

POST — не идемпотентный. Каждый POST /orders создаёт новый заказ. Поэтому retry без Idempotency-Key создаёт дубли.

Idempotency-Key — заголовок, делающий POST идемпотентным:

POST /payments
Idempotency-Key: 8a1b2c3d-...
{
  "amount": 1000,
  "to": "user-123"
}

Сервер хранит результат первого POST с этим ключом и при повторном — возвращает тот же ответ, не повторяя действие. Стандарт в Stripe API, обязателен в платежах. В ТЗ обязательно описывать: сколько хранится результат, что считать «тем же ключом», как реагировать на конфликт (тот же ключ, другое тело — 422).

Коды 2xx и 3xx

200 OK — успех с body. Универсальный.

201 Created — создан новый ресурс. Возвращать с заголовком Location: /users/123 (URI нового). Используется для POST/PUT, создающих ресурс.

202 Accepted — принято, обрабатывается асинхронно. Body содержит ссылку на статус (/jobs/abc/status).

204 No Content — успех без body. Удобно для DELETE и PUT, где клиенту не нужен результат.

3xx — редиректы:

  • 301 Moved Permanently — старый URL умер, кешируется навсегда (используется для SEO/смены доменов)
  • 302 Found / 307 Temporary Redirect — временный редирект
  • 304 Not Modified — ресурс не менялся (с If-None-Match/ETag), не пересылать body

В API чаще всего: 200, 201, 204. Остальные используются точечно.

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Коды 4xx — частые ошибки выбора

Самые частые путаницы на собесе:

400 Bad Request vs 422 Unprocessable Entity:

  • 400 — синтаксическая ошибка запроса: невалидный JSON, отсутствует обязательное поле, неверный тип. Запрос не может быть распарсен.
  • 422 — синтаксис ок, но семантика невалидна: «дата окончания раньше начала», «email уже занят», «недостаточно средств».

Не все API используют 422. Старые в основном возвращают 400 на всё. На собесе можно сказать: «Современная практика — 422 для бизнес-валидации, 400 для парсинга».

401 Unauthorized vs 403 Forbidden:

  • 401 — нет авторизации (нет токена, токен невалиден, истёк). Клиент должен прислать токен и попробовать снова. Сервер обязан вернуть WWW-Authenticate заголовок.
  • 403 — авторизация есть, но прав не хватает. Клиент авторизован, но не имеет доступа к этому ресурсу. Повтор не поможет, нужно эскалировать права.

Память: 401 — «я не знаю, кто ты», 403 — «знаю, но тебе нельзя».

404 Not Found vs 403:

  • 404 — ресурса не существует, либо клиент не должен знать о его существовании.
  • В чувствительных API (банк, медицина) часто возвращают 404 вместо 403, чтобы не раскрывать наличие чужих ресурсов. Если злоумышленник видит 403 на /users/12345 — он знает, что user 12345 существует. С 404 — нет.

409 Conflict — конфликт с текущим состоянием. Классические кейсы: optimistic lock провалился (If-Match не совпал с ETag), email уже занят на регистрации (если не выбираем 422), дубликат Idempotency-Key с другим телом.

429 Too Many Requests — rate limit. Возвращать с Retry-After (секунды).

Коды 5xx и retry

500 Internal Server Error — необработанное исключение на сервере. Не давать никакой информации о внутренностях в body.

502 Bad Gateway — прокси/балансировщик не получил валидный ответ от upstream.

503 Service Unavailable — сервис временно недоступен (maintenance, перегрузка). С Retry-After.

504 Gateway Timeout — upstream не ответил за таймаут.

Правило retry:

  • 5xx — ретраить с exponential backoff (1s, 2s, 4s, 8s)
  • 408, 429, 503, 504 — ретраить
  • 4xx (кроме 408, 429) — не ретраить, проблема в запросе клиента

Идемпотентность важна: ретраить можно безопасно только идемпотентные запросы (GET, PUT, DELETE) или POST с Idempotency-Key.

Антипаттерн: возвращать 500 на бизнес-ошибки. «Не хватает средств» — это не 500, это 422 или 409. Клиент увидит 500, начнёт retry, бэкенд начнёт пытаться списать деньги повторно.

Частые ошибки

Использовать GET с body. Стандарт допускает, прокси/CDN/клиенты могут body выкинуть. Параметры — query string, длина <2 КБ.

PUT для частичного апдейта. PUT — полная замена. Чтобы поменять одно поле — PATCH с RFC 7396 (JSON Merge Patch) или RFC 6902 (JSON Patch).

Возвращать 200 на ошибку с {success: false} в body. Клиент-парсер пишет «if status == 200 then OK», ошибка проходит мимо. HTTP-код ДОЛЖЕН отражать результат.

Перепутать 401/403. При 401 клиент пытается обновить токен. При 403 — показывает «нет доступа». Перепутали — клиент в бесконечном цикле refresh.

Возвращать 500 при бизнес-валидации. Любая ретраи-логика на стороне клиента превратит «не хватает денег» в «попробуй ещё 8 раз списать».

Описывать в ТЗ контракт без error-кодов. Только 200 — half-API. Описывать все 4xx и 5xx с примерами тел.

Игнорировать Idempotency-Key в платежах. Без него любой ретрай при таймауте сети — двойное списание.

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

FAQ

POST /users/{id}/delete или DELETE /users/{id}?

DELETE — стандарт REST. POST на endpoint c глаголом — устаревший RPC-стиль, нарушает HTTP-семантику и REST-принципы. Допустимо только если действие не выражается в CRUD (например, POST /payments/{id}/refund).

Можно ли вернуть 201 на PUT?

Да, если PUT создал ресурс (классический PUT работает как «создай или замени» по полному URI). Если ресурс уже существовал и был заменён — 200 или 204. На собесе уточнить: «PUT может и создавать, и заменять; код зависит от того, что произошло».

Что вернуть на POST /orders при создании?

201 Created с body нового заказа и заголовком Location: /orders/{id}. Альтернатива — 200 с body, если URL ресурса не известен сразу (асинхронное создание).

Чем PATCH отличается от PUT?

PUT заменяет ресурс полностью: все поля в теле, отсутствующие — затираются. PATCH меняет только указанные поля. PATCH сложнее в реализации (нужен формат — JSON Merge Patch RFC 7396 или JSON Patch RFC 6902), на собесе спрашивают, какой формат используем.

Как описать race condition в ТЗ?

Через optimistic lock: клиент при PUT/PATCH присылает If-Match: <ETag>, сервер проверяет, что версия не изменилась, иначе 412 Precondition Failed (или 409 Conflict). В ТЗ описывать сценарий: «при ошибке клиент перечитывает ресурс и применяет изменения снова».

Это официальная информация?

Нет. Статья основана на RFC 9110 (HTTP Semantics), RFC 5789 (PATCH), RFC 8594 (Idempotency-Key draft) и общей практике REST-дизайна. Конкретные требования к API могут отличаться.


Тренируйте системный анализ — откройте тренажёр с 1500+ вопросами для собесов.