Перейти к содержанию

Условные GET-запросы

Мы умеем получать курс и читать список по снимку. Теперь рассмотрим повторное чтение одной записи. Если представление не изменилось, пересылать прежнее тело каждый раз необязательно. Сервер может выдать валидатор, а клиент — спросить, отличается ли текущий выбранный вариант от уже сохранённого.

В этом уроке используем ETag и If-None-Match. Результат — вы понимаете ветки 200 и 304, храните метку вместе с правильным представлением и не пытаетесь прочитать JSON из ответа без тела. Примеры являются моделью условного обмена, а не полученными измерениями скорости или сетевыми сообщениями.

Первое чтение с меткой

Клиент получает JSON c001 обычным GET с Accept: application/json. Предполагаемый ответ начального состояния:

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "c001-json-r1"
Cache-Control: public, no-cache
Vary: Accept

{"id":"c001","title":"Современный JavaScript","topic":"frontend","lessons":40}

Клиент сохраняет тело, точное значение ETag и сведения, необходимые для соответствия запросу. Метка не является кратким названием курса и не заменяет данные. Если сохранить только строку ETag, при 304 нечего будет показать. Условное чтение имеет смысл, когда клиент действительно располагает пригодным представлением.

В нашем стенде JSON сериализуется определённым образом, без дополнительной компрессии, и метка сильная. Она соответствует байтам выбранного JSON. Другой HTML-вариант имеет собственный ETag. Клиент не переносит метку между форматами по общему id, потому что валидатор относится к представлению, а не просто к факту существования курса.

Политика public, no-cache разрешает хранение публичного ответа с проверкой перед повторным использованием. Она не объявляет отсутствие кеша. Правила передачи статических ресурсов уже рассматривались в уроке производительности; здесь мы проектируем конкретную условную ветку API и её смысл для программы.

Повтор с If-None-Match

Клиент повторяет чтение того же JSON и передаёт сохранённую метку:

GET /api/courses/c001 HTTP/1.1
Host: catalog.example
Accept: application/json
If-None-Match: "c001-json-r1"

If-None-Match означает, что обычное содержимое нужно, когда выбранное представление не совпадает с указанным валидатором. Если оно по-прежнему соответствует r1, сервер возвращает 304 Not Modified. Покажем смысловые поля такого ответа полностью:

HTTP/1.1 304 Not Modified
ETag: "c001-json-r1"
Cache-Control: public, no-cache
Vary: Accept

После пустой строки нет тела. Нельзя добавить {"unchanged":true} или снова отправить курс: статус 304 не содержит обычное представление. Клиент использует сохранённые данные и обновляет подходящие метаданные по правилам кеша. Сетевое обращение всё равно произошло, поэтому это не равнозначно отсутствию ожидания или запросу из памяти без связи с сервером.

В реальном сообщении сервер включит обязательные применимые поля, например Date; учебная схема, как и остальные листинги серии, сокращена до обсуждаемых сведений. Content-Length намеренно не задан. Если его вообще передавать для 304, значение соответствует размеру возможного ответа 200, а не нулевому телу 304. Проще не придумывать число, которое объясняет не ту величину.

Когда данные изменились

В отдельном продолжении сценария редактор меняет title на «JavaScript для веба». Новое выбранное JSON-представление получает метку "c001-json-r2". Клиент всё ещё посылает прежний If-None-Match r1. Теперь ожидается обычный успешный ответ с новым телом:

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "c001-json-r2"
Cache-Control: public, no-cache
Vary: Accept

{"id":"c001","title":"JavaScript для веба","topic":"frontend","lessons":40}

Клиент заменяет сохранённые данные и валидатор согласованной парой. Нельзя обновить ETag, оставив старое title: при следующем совпадении сервер законно вернёт 304, а программа покажет устаревшее тело под новой меткой. Сохранение пары является частью корректного клиентского кеша.

If-None-Match не сообщает, какое изменение произошло. Это условие получения, а не журнал редакторских действий. Если интерфейсу нужна история, понадобится другой ресурс и форма данных. Непрозрачную метку не следует разбирать, вычислять или использовать как доказательство количества обновлений.

Ответ 404 после удаления также не превращается в 304 только потому, что у клиента осталась прежняя метка. Она относилась к ранее доступному представлению; сервер сначала учитывает цель и текущий результат. В нашем публичном API отсутствие записи объясняется обычной проблемой course-not-found. Клиент больше не показывает старую карточку как подтверждённую текущую версию.

Когда сохранённого тела нет

Представим, что программа записала ETag, но пользователь очистил сами данные курса. Отправка прежнего If-None-Match может получить 304, и тогда клиент не имеет пригодного тела для показа. Правильный выход — выполнить обычное чтение без этого условия, а не заполнить карточку пустыми значениями и назвать её актуальной. Метка полезна только вместе с сохранённым представлением.

Аналогичная проблема возникает, если локальный кеш перепутал форматы. Сохранённый HTML нельзя обработать как JSON при подтверждении соответствующей HTML-метки. Поэтому ключ клиентского хранения учитывает не только id, но и существенные условия выбора. Даже небольшой API должен хранить эти связи, если приложение самостоятельно управляет условными запросами.

Смена текущего snapshot списка не является сменой ETag единичного курса. Список и запись — разные ресурсы с разными представлениями. Курсор s1 объясняет границы обхода, а метка c001-json-r1 — состояние выбранного JSON. Нельзя вернуть 304 для всего списка только потому, что первый курс сохранил прежнюю метку. Для условного списка понадобился бы валидатор именно его полного выбранного ответа.

Условное чтение не задаёт срок свежести

ETag не говорит, сколько минут можно использовать тело без проверки. Он помогает сравнить представления при условном обращении. Срок и допустимость использования определяются политикой кеша. Поэтому наличие метки без Cache-Control не следует трактовать как указание бесконечно держать данные или проверять их каждую секунду.

Наш no-cache выбирает проверку перед повторным использованием, но не является единственным возможным продуктовым решением. Небольшой срок свежести способен уменьшить обращения при допустимой задержке обновления; для личного ответа могут понадобиться иные ограничения хранения. Разделите вопрос «похож ли нынешний вариант на сохранённый» и вопрос «когда следует спросить сервер». Это разные части договора.

Из-за такого разделения условный GET нельзя оценить одной выдуманной цифрой ускорения. Выгода зависит от размера тела, стоимости проверки, соединения и клиентского сценария. Здесь результат функциональный: при совпадении не передаётся тело, при различии приходит новая согласованная пара данных и валидатора. Реальные измерения потребуют отдельного будущего опыта.

Сильное и слабое сравнение

Сильная метка обозначается без префикса W/, например "c001-json-r1". Слабая имеет форму W/"c001-json-r1" и не обещает точного совпадения байтов. Для If-None-Match применяется слабое сравнение: непрозрачная часть может совпасть независимо от того, помечены ли одна или обе метки слабостью.

Это правило подходит проверке сохранённого представления по объявленной эквивалентности. Однако слабую метку нельзя превратить в сильную удалением W/. Префикс сообщает свойство, заданное сервером. Его удаление в клиенте не создаёт отсутствующую гарантию байтовой идентичности и позже не даст корректного If-Match для защиты изменения. Условие If-None-Match.

Если клиент сменил Accept на HTML, он должен использовать сохранённый HTML и соответствующую метку. Отправка JSON-метки при выборе HTML обычно приводит к отсутствию совпадения и получению HTML с 200. Программа не должна воспринимать это как «курс точно изменён»: просто был выбран другой вариант. То же внимание понадобится при языках и Content-Encoding.

У браузера есть собственный HTTP-кеш, который может сформировать условный запрос и обработать 304 автоматически. Если приложение ведёт свой кеш и вручную отправляет If-None-Match, оно должно явно предусмотреть ветку без тела. Нельзя ожидать одинаковой видимости 304 в любом API браузера и любой настройке загрузки; будущий опыт требует наблюдения фактической цепочки.

Теперь чтение имеет три понятных исхода: новое представление, подтверждённое сохранённое представление и отказ цели. Условный GET уменьшает передачу тела, но не защищает последующую редакторскую запись от конкурента. Следующий урок использует сильный валидатор в другом условии, чтобы применить изменение только к той версии, которую редактор действительно прочитал.

Оглавление курса