Суть Читай реальные HTTP-обмены — статус-строку, заголовки и тело вместе — и определяй, что ответ на самом деле говорит, и фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Статус-коды диагностируют на проводе, в сыром обмене. Читай каждую пару запрос/ответ — строку, заголовки, тело — и решай, что она реально говорит и что сеньор поменял бы первым.
Цель
Отработай цикл чтения на проводе: замечай, когда статус и тело противоречат друг другу, когда отсутствует заголовок и когда код приписывает вину не той стороне — и называй фикс.
Апстрим словил таймаут, а статус-строка — 200. Каково последствие и фикс?
Heads-up Только клиенты, которые парсят и проверяют твой кастомный конверт, обнаружат это. Кэши, дашборды и retry-middleware никогда не читают тело; они ветвятся по 200 и считают полный отказ идеальным здоровьем. Правду должен нести статус.
Heads-up Спрятать 5xx за 200 не убирает тревогу; оно убирает сигнал, который вёл бы к корректному автоматическому восстановлению. Ошибка должна всплыть в статусе, чтобы система отреагировала.
Heads-up Положить число в тело ничего не меняет для машин, которые ветвятся по HTTP-статус-строке. Сам статус-код должен быть 5xx.
Обмен 2 — 401 vs 403
DELETE /tenants/acme/users/77 HTTP/1.1Authorization: Bearer eyJhbGc... # валидный, непросроченный токен пользователя тенанта 'globex'HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer{ "error": "you may not delete users in another tenant" }
Викторина
Completed
Токен валиден; вызывающему просто не хватает прав на другой тенант. Что не так с этим ответом и каким он должен быть?
Heads-up 401 и 403 оба про auth, но значат разное. 401 = не аутентифицирован (фикс: войти / рефрешнуть). 403 = аутентифицирован, но не разрешено (смена креденшелов не поможет). Токен здесь валиден, значит, это 403.
Heads-up Запрос корректно сформирован; с его синтаксисом всё в порядке. Проблема в правах, а не в структуре. 400 сказал бы клиенту чинить форму запроса, что нерелевантно.
Heads-up Нет конфликта состояния ресурса — пользователь существует и запрос распарсился. Вызывающему просто не разрешено. Это 403, а не 409.
Обмен 3 — 503 без Retry-After и горячий цикл ретраев
GET /catalog/items?page=3 HTTP/1.1HTTP/1.1 503 Service UnavailableContent-Type: application/json{ "error": "overloaded, shedding load" }# поведение клиента: тут же переотправляет GET в тесном цикле, без задержки
Викторина
Completed
Сервер сбрасывает нагрузку через 503, но не прислал Retry-After, а клиент ретраит мгновенно. Каковы два фикса — по одному на сторону?
Heads-up 503 — единственный 5xx, который должен нести Retry-After именно для того, чтобы клиенты отступали разумно. Опустить его — оставить клиентов гадать; сервер разделяет вину за шторм.
Heads-up Даже без Retry-After долбить заведомо перегруженный сервер без задержки — учебный способ держать его перегруженным. Клиент обязан отступать с джиттером в любом случае.
Heads-up GET идемпотентен, поэтому ретрай на 503 безопасен и корректен — проблема в тайминге (мгновенный цикл) и отсутствующем Retry-After, а не в том, ретраить ли вообще.
Обмен 4 — условное обновление и 412
PUT /docs/42 HTTP/1.1If-Match: "v7"Content-Type: application/json{ "title": "Q3 plan", "body": "..." }HTTP/1.1 412 Precondition Failed# хранимый ETag теперь "v9" — кто-то сохранил дважды с момента последнего чтения клиентом
Викторина
Completed
Условный PUT возвращает 412, потому что ETag сдвинулся с v7 на v9. Что это говорит клиенту и что ему делать?
Heads-up 412 — про заголовок-предусловие (If-Match), вычислившееся в ложь, а не про тело. Тело распарсилось нормально. Фикс — перечитать и обновить предусловие, а не чинить JSON.
Heads-up 412 говорит, что предусловие не выполнилось (ETag сдвинулся), а значит, ресурс ещё существует, но изменился. Удаление всплыло бы иначе. Перечитай, чтобы увидеть текущее состояние.
Heads-up Убрать If-Match — отключить защиту оптимистичной конкурентности и молча перезаписать чужие правки v8/v9 — ровно тот баг lost update, который предотвращает предусловие. Вместо этого сведи правку с новой версией.
Итог
На проводе читай статус-строку, заголовки и тело как единое целое. 200 с телом-ошибкой лжёт каждой машине, ветвящейся по статусу, — верни реальный 5xx. 401 vs 403 упирается в аутентификацию vs авторизацию, и ошибка тут зацикливает клиента. 503 должен нести Retry-After, а клиент без него обязан отступать с джиттером, а не долбить. 412 — это работающая оптимистичная конкурентность: перечитай, сведи правку с новым ETag и повтори условную запись, а не форсируй перезапись.