Цифра класса несёт в себе контракт
Каждый HTTP-ответ начинается с трёхзначного кода, и первая цифра — единственная часть, которую клиент обязан понимать. RFC 9110 прямо об этом говорит: клиент, получивший неизвестный 499, должен обрабатывать его точно так же, как 400, потому что класс определяет семантику, а оставшиеся две цифры лишь уточняют её. Именно это правило сохраняет расширяемость протокола. Прокси, написанный в 2005 году, всё ещё может правильно маршрутизировать 451, не зная, что такое юридическая цензура.
Пять классов чётко делятся по тому, кто отвечает за происходящее дальше. 1xx — это промежуточный ответ, поэтому настоящий ответ ещё идёт, а соединение остаётся открытым. 2xx означает, что запрос успешен и клиент может остановиться. 3xx возвращает перенаправление, поэтому клиент должен повторить попытку в другом месте. 4xx винит запрос, поэтому его повторение без изменений снова завершится неудачей. 5xx винит сервер, поэтому идентичный запрос при повторе может пройти. Вот почему логика повтора опирается на класс, а не на конкретный код: повторять 503 разумно, повторять 403 — это ошибка.
Коды, которые постоянно путают
Различие между 301 и 308 или 302 и 307 не стилистическое. Старая пара позволяет клиенту переписать POST в GET при следовании перенаправлению, что браузеры исторически делали. Новая пара запрещает такую перезапись и сохраняет и метод, и тело. Если вы перенесёте конечную точку формы с помощью 301, некоторые клиенты молча преобразуют отправку в GET и потеряют полезную нагрузку. Используйте 308 или 307 для всего, что не идемпотентно.
401 и 403 путают похожим образом. 401 означает, что запрос не был аутентифицирован и должен прийти с заголовком-запросом WWW-Authenticate, сообщающим клиенту, как повторить попытку с учётными данными. 403 означает, что сервер понял, кто вы, и всё равно отказывает. Отправка 401 без заголовка-запроса — нарушение спецификации, а отправка 403, когда пользователь просто забыл войти, сильно затрудняет отладку.
Класс 4xx также содержит действительно полезную пару: 400 против 422. 400 означает, что запрос вообще не удалось разобрать, то есть синтаксис сломан. 422 означает, что синтаксис разобрался нормально, но содержимое не прошло валидацию. Разделение этих двух превращает непрозрачную ошибку в понятную для клиентов API.
Причинные фразы — только для людей
Текст рядом с кодом, например Not Found или I'm a teapot, — это причинная фраза. HTTP/1.1 передаёт её в строке состояния, но HTTP/2 и HTTP/3 удалили её полностью, поэтому современное соединение часто несёт только число без фразы. Ничто в клиенте не должно ветвиться по этой строке. Она существует, чтобы человек, читающий лог или сырой трассировку, видел, что произошло, без таблицы соответствий.
Некоторые коды также более ограничены, чем кажется. 204 и 304 не должны содержать тело сообщения, поэтому посредник, добавляющий его, создаёт неверный ответ. Ответ HEAD никогда не несёт тела независимо от кода. Эти правила фрейминга важны, потому что тело там, где его не ждут, рассинхронизирует повторное использование соединения, что проявляется как странная межзапросовая порча, а не как очевидная ошибка.