Кратко

  • Код 428 разрешает origin-серверу отклонить изменение без обязательного условия. Условная запись становится не добровольной осторожностью клиента, а политикой допуска.
  • Обычный пример — If-Match с сильным ETag. 428 означает, что условие не предъявлено; 412 — что предъявленное условие оказалось ложным.
  • Статус ничего не блокирует и не сливает. Нужны корректные валидаторы, атомарные сравнение и commit, а после конфликта — осмысленное согласование данных.

Потеря, скрытая двумя успешными ответами

Два редактора открывают версию 7. Первый исправляет текст и сохраняет версию 8. Второй продолжает работать со старой копией, а затем отправляет документ целиком. Если сервер проверяет только права, формат и адрес, обе операции могут завершиться успехом. Исправление первого редактора при этом исчезает.

Для lost update не нужен злоумышленник. Второй запрос назвал цель и содержимое, но не состояние, из которого возникло его решение. «Заменить ресурс» и «заменить, только если он всё ещё соответствует прочитанной мной версии» — разные полномочия.

Условные запросы существовали в HTTP и раньше. Валидаторы чаще всего вспоминают в связи с кэшем: клиент возвращает метку и не скачивает неизменившееся представление. Та же улика способна охранять запись. Не хватало точного ответа origin-сервера, который вообще не желает принимать безусловную форму.

В апреле 2012 года RFC 6585 определил 428 Precondition Required. Он означает, что сервер происхождения требует условный запрос. Типичный сценарий в документе — клиент читает состояние, меняет его и отправляет назад после того, как третья сторона уже внесла своё изменение.

Осторожность становится входным правилом

Если при чтении клиент получил сильную метку "v7", заголовок If-Match: "v7" превращает память в проверяемое утверждение. Метод выполняется лишь тогда, когда выбранное текущее представление сохраняет этот ETag. Появление "v8" делает условие ложным, и изменение не должно происходить.

Внимательный клиент мог поступать так и без 428. Новый код позволил origin-серверу объявить это обязательным правилом ресурса. Пример RFC предлагает попробовать If-Match, а ответ рекомендуется снабдить объяснением успешной повторной подачи.

Это не универсальное требование одного поля. If-Match: * утверждает, что представление должно существовать. If-None-Match: * утверждает отсутствие и помогает не затереть параллельное создание. При отсутствии ETag условие можно выразить через If-Unmodified-Since. Конкретный ресурс обязан объяснить, какое доказательство принимает.

Главное изменение — в распределении контроля. Владелец актуального состояния получил общее средство потребовать от автора записи назвать наблюдавшееся прошлое.

Нет условия и неверно условие — разные события

428 Precondition Required относится к допуску. Политика требует предикат, но запрос не принёс приемлемого. Метод с эффектами не начинается.

412 Precondition Failed относится к вычислению. Предикат присутствовал, origin сравнил его с текущим состоянием, и результат оказался ложным. Клиент назвал прошлое, однако оно больше не совпадает с настоящим.

Разделение полезно для эксплуатации. Рост 428 может указывать на старый SDK, неясную документацию или шлюз, удаляющий условные поля. Рост 412 может означать долгие сессии, реальную конкуренцию либо излишне нестабильные теги. Одно число ошибок скрывает различие причин.

428 не является разновидностью 409. Код 409 способен сообщать о смысловом конфликте с ресурсом; 428 требует доказательство до попытки опасной записи. Это и не WebDAV 423: никакая блокировка не создаётся.

Почему совпадение обязано быть сильным

Современная семантика HTTP в RFC 9110 требует для If-Match сильного сравнения. Слабая метка может обозначать эквивалентность, достаточную для кэша. Но пишущий клиент намерен остановить метод, если изменились любые данные представления.

Поэтому генерация ETag становится частью внешнего договора. Если защищённое состояние изменилось, а тег нет, устаревшая запись пройдёт. Если тег меняется из-за несущественного порядка сериализации, возникнут ложные конфликты. Требовать If-Match, не выдавая пригодный сильный ETag, означает превратить 428 в тупик.

У времени точность ниже. If-Unmodified-Since помогает без метки, но RFC 9110 считает If-Match более точной заменой и игнорирует дату, когда присутствуют оба поля. Разрешение часов и способ формирования времени ограничивают такой предикат.

Сравнение должно касаться commit

HTTP предписывает origin-серверу проверять предусловия после обычных проверок и непосредственно перед действием метода. Посредник не владеет актуальным состоянием и не вправе решать, истинно ли If-Match; он пересылает запрос.

Внутри приложения граница не должна разрываться. Если обработчик сравнил версию, закрыл транзакцию, а записал позже, конкурент успеет войти между проверкой и commit. На проводе запрос выглядит условным, в реализации возвращается гонка time-of-check/time-of-use.

Сравнение и действие должны быть атомарными: колонка версии, compare-and-swap или эквивалентная транзакция. HTTP задаёт наблюдаемое обещание, но не создаёт изоляцию базы данных.

Важно и соответствие области. ETag отрисованной страницы не обязательно защищает несколько внутренних записей. Запрос, который ещё и посылает внешнее сообщение, пересекает дополнительную границу. 428 не превращает эти эффекты в распределённую транзакцию.

Отклонение ещё не выполняет слияние

После 412 клиент может взять только свежий ETag и приложить его к старому полному телу. Новое условие выполнится, но промежуточное изменение никто не рассмотрит. Видимый конфликт превратится в формально разрешённую перезапись.

Правильное восстановление обычно заново читает состояние, сравнивает ожидающее намерение и выбирает слияние, отказ либо явную замену. Патч по полям или коммутативная операция уменьшает поверхность конфликта. Иногда решение остаётся человеку. 428 создаёт такую ветвь, но не принимает решение.

RFC 6585 также делает статус необязательным и предупреждает, что клиенты не могут полагаться на 428 для предотвращения всех потерянных обновлений. Если API предлагает условную запись, ответственный клиент должен применять её сам.

Отказ нельзя превращать в кэшированную политику

Ответ 428 запрещено сохранять в кэше. Он описывает конкретную попытку, не прошедшую правило допуска, а не переиспользуемое представление цели. Старый кэшированный отказ позволил бы посреднику присвоить решение origin-сервера.

Полезный ответ называет требуемое поле, путь получения текущего валидатора и необходимость согласовать содержимое после нового чтения. Одного названия статуса недостаточно.

Реестр IANA по-прежнему содержит 428 Precondition Required со ссылкой на RFC 6585. Короткая формула хранит важное правило: прежде чем заменить настоящее, запись должна раскрыть прошлое, которое даёт ей основание.

Источники и пределы

Основа статьи — RFC 6585, RFC 9110 и реестр IANA. Они устанавливают семантику, порядок и ограничения, но не измеряют современное внедрение, ущерб, все клиентские библиотеки или конкретные инциденты. 428 не является блокировкой, слиянием, транзакцией или гарантией exactly-once.