Условные запросы
Условные запросы HTTP
В HTTP существует понятие условных запросов, при которых результат и даже успешность запроса могут зависеть от сравнения затрагиваемых ресурсов с валидатором. Такие запросы полезны для проверки содержимого кэша, чтобы получать его только в том случае, если оно отличается от копии, уже доступной браузеру. Условные запросы также помогают проверять целостность документа при возобновлении загрузки или предотвращать потерю обновлений при отправке или изменении документа на сервере.
Принципы
Условные запросы HTTP — это запросы, обработка которых зависит от значения определённых заголовков. Эти заголовки задают предварительное условие, и результат запроса будет различаться в зависимости от того, выполнено оно или нет.
Различное поведение определяется используемым методом запроса и набором заголовков, задающих предварительное условие:
- для безопасных методов, таких как
GET, которые обычно используются для получения документа, условный запрос позволяет отправить документ, только если это необходимо. Это экономит пропускную способность. - для небезопасных методов, таких как
PUT, которые обычно используются для загрузки документа, условный запрос позволяет отправить документ, только если исходная версия, на основе которой он подготовлен, совпадает с версией, хранящейся на сервере.
Валидаторы
Все условные заголовки проверяют, соответствует ли ресурс на сервере определённой версии. Для этого условные запросы должны указывать версию ресурса. Сравнивать весь ресурс побайтно непрактично, а иногда и нежелательно, поэтому запрос передаёт значение, описывающее версию. Такие значения называются валидаторами и бывают двух видов:
- дата последнего изменения документа — дата last-modified.
- непрозрачная строка, уникально идентифицирующая каждую версию, — тег сущности, или ETag.
Сравнивать версии одного и того же ресурса непросто: в зависимости от контекста существуют два вида проверки на равенство:
- Строгая проверка используется, когда требуется побайтовое совпадение, например при возобновлении загрузки.
- Нестрогая проверка используется, когда пользовательскому агенту нужно лишь определить, совпадает ли содержимое двух ресурсов. Ресурсы могут считаться одинаковыми, даже если между ними есть небольшие различия, например разные рекламные объявления или подвал с другой датой.
Тип проверки не зависит от используемого валидатора. И Last-Modified, и ETag поддерживают оба типа проверки, хотя сложность их реализации на стороне сервера может различаться. По умолчанию HTTP использует строгую проверку и указывает, когда можно использовать нестрогую.
Строгая проверка
Строгая проверка гарантирует, что ресурс побайтно идентичен тому, с которым его сравнивают. Она обязательна для некоторых условных заголовков и используется по умолчанию для остальных. Строгая проверка очень требовательна и может быть сложной для обеспечения на стороне сервера, но она гарантирует отсутствие потери данных, иногда в ущерб производительности.
С помощью Last-Modified довольно сложно получить уникальный идентификатор для строгой проверки. Часто для этого используют ETag с хешем MD5 ресурса (или его производной).
Примечание: Поскольку изменение кодировки содержимого требует изменения ETag, некоторые серверы изменяют ETag при сжатии ответов от исходного сервера (например, обратные прокси-серверы). По умолчанию Apache Server добавляет к ETag название метода сжатия (-gzip), однако это можно настроить с помощью директивы DeflateAlterETag.
Нестрогая проверка
Нестрогая проверка отличается от строгой тем, что считает две версии документа одинаковыми, если их содержимое эквивалентно. Например, страница, отличающаяся от другой только датой в подвале или рекламными объявлениями, при нестрогой проверке будет считаться идентичной другой странице. При строгой проверке эти же две версии считаются различными. Создание системы ETag с нестрогой проверкой очень полезно для оптимизации производительности кэша, но может быть сложным, поскольку требует понимания важности различных элементов страницы.
Условные заголовки
Некоторые заголовки HTTP, называемые условными, позволяют выполнять условные запросы. К ним относятся:
If-Match-
Запрос выполняется успешно, если
ETagудалённого ресурса совпадает с одним из указанных в этом заголовке. Выполняется строгая проверка. If-None-Match-
Запрос выполняется успешно, если
ETagудалённого ресурса отличается от всех указанных в этом заголовке. Выполняется нестрогая проверка. If-Modified-Since-
Запрос выполняется успешно, если дата
Last-Modifiedудалённого ресурса более поздняя, чем дата, указанная в этом заголовке. If-Unmodified-Since-
Запрос выполняется успешно, если дата
Last-Modifiedудалённого ресурса более ранняя или совпадает с датой, указанной в этом заголовке. If-Range-
Похож на
If-MatchилиIf-Unmodified-Since, но может содержать только один ETag или одну дату. Если условие не выполнено, запрос диапазона завершается неудачей, и вместо ответа206 Partial Contentотправляется ответ200 OKс ресурсом целиком.
Примеры использования
>Обновление кэша
Наиболее распространённый пример использования условных запросов — обновление кэша. Если кэш пуст или отсутствует, запрошенный ресурс отправляется с кодом состояния 200 OK.
Вместе с ресурсом в заголовках отправляются валидаторы. В этом примере отправляются и Last-Modified, и ETag, но можно было бы отправить только один из них. Эти валидаторы сохраняются в кэше вместе с ресурсом (как и все заголовки) и используются для формирования условных запросов, когда кэш устаревает.
Пока кэш не устарел, запросы не отправляются. Но когда он устаревает, дальнейшее поведение в основном определяется заголовком Cache-Control: клиент не использует сохранённое значение напрямую, а отправляет условный запрос. Значение валидатора используется в качестве параметра заголовков If-Modified-Since и If-None-Match.
Если ресурс не изменился, сервер отправляет ответ 304 Not Modified. Кэш снова становится актуальным, и клиент использует сохранённый ресурс. Хотя для этого требуется обмен запросом и ответом, который расходует некоторые ресурсы, это эффективнее, чем повторно передавать по сети весь ресурс.
Если ресурс изменился, сервер просто отправляет ответ 200 OK с новой версией ресурса (как если бы запрос не был условным). Клиент использует этот новый ресурс и сохраняет его в кэше.
Помимо настройки валидаторов на стороне сервера, этот механизм прозрачен: все браузеры управляют кэшем и отправляют такие условные запросы без каких-либо дополнительных действий со стороны веб-разработчиков.
Целостность частично загруженного файла
Частичная загрузка файлов — это функция HTTP, которая позволяет возобновлять прерванные операции, экономя пропускную способность и время за счёт сохранения уже полученных данных:
Сервер, поддерживающий частичную загрузку, сообщает об этом, отправляя заголовок Accept-Ranges. После этого клиент может возобновить загрузку, отправив заголовок Range с недостающими диапазонами:
Принцип прост, но есть потенциальная проблема: если загруженный ресурс изменился между двумя загрузками, полученные диапазоны будут соответствовать разным версиям ресурса, и итоговый документ окажется повреждён.
Чтобы этого избежать, используют условные запросы. Для диапазонов есть два способа сделать это. Более гибкий способ использует заголовки If-Unmodified-Since и If-Match, а сервер возвращает ошибку, если предварительное условие не выполнено; затем клиент начинает загрузку заново:
Этот метод работает, но если документ изменился, он добавляет дополнительный обмен запросом и ответом. Это снижает производительность, поэтому в HTTP есть специальный заголовок, позволяющий избежать такой ситуации: If-Range:
Это решение эффективнее, но немного менее гибкое, поскольку в условии можно использовать только один ETag. Такая дополнительная гибкость требуется редко.
Предотвращение потери обновлений с помощью оптимистичной блокировки
Распространённая операция в веб-приложениях — обновление удалённого документа. Это часто встречается в файловых системах и системах контроля версий, но такой механизм нужен любому приложению, которое позволяет хранить удалённые ресурсы. Веб-сайтам, например вики и другим CMS, он также необходим.
Реализовать это можно с помощью метода PUT. Клиент сначала считывает исходные файлы, изменяет их, а затем отправляет на сервер:
К сожалению, при учёте параллельных операций ситуация становится менее предсказуемой. Пока один клиент локально изменяет свою копию ресурса, второй клиент может получить тот же ресурс и внести изменения в свою копию. Далее происходит неприятное: когда клиенты отправляют изменения на сервер, отправка второго клиента отменяет изменения первого, поскольку второй клиент не знает об изменениях первого. Решение о том, кто победил, не сообщается другой стороне. То, чьи изменения сохранятся, зависит от скорости отправки; она определяется производительностью клиентов и сервера и даже человеком, который редактирует документ на клиенте. Победитель будет меняться от раза к разу. Это состояние гонки, приводящее к проблемному поведению, которое трудно обнаружить и отладить:
Решить эту проблему, не доставив неудобств одному из двух клиентов, невозможно. Однако потери обновлений и состояния гонки необходимо предотвращать. Нам нужны предсказуемые результаты, и мы ожидаем, что клиенты будут получать уведомления об отклонении своих изменений.
Условные запросы позволяют реализовать алгоритм оптимистичной блокировки (используемый большинством вики и систем контроля версий). Его идея состоит в том, чтобы разрешить всем клиентам получать копии ресурса и изменять их локально, а затем контролировать параллельный доступ, разрешая отправить обновление первому клиенту. Все последующие обновления, основанные на уже устаревшей версии ресурса, отклоняются:
Для этого используют заголовки If-Match или If-Unmodified-Since. Если ETag не совпадает с ETag исходного файла или файл был изменён после получения, изменение отклоняется с ошибкой 412 Precondition Failed. Затем клиент должен обработать ошибку: либо уведомить пользователя, чтобы тот начал заново (на этот раз с самой новой версией), либо показать ему различия между двумя версиями, помогая решить, какие изменения оставить.
Первая загрузка ресурса
Первая загрузка ресурса — особый случай описанной выше ситуации. Как и при любом обновлении ресурса, здесь возникает состояние гонки, если два клиента пытаются выполнить операцию примерно одновременно. Чтобы этого избежать, можно использовать условные запросы: добавив If-None-Match со специальным значением *, представляющим любой ETag. Запрос будет выполнен успешно, только если ресурса ещё не существовало:
If-None-Match будет работать только с серверами, совместимыми с HTTP/1.1 и более поздними версиями. Если вы не уверены в совместимости сервера, сначала нужно отправить к ресурсу запрос HEAD, чтобы это проверить.
Заключение
Условные запросы — важная возможность HTTP, позволяющая создавать эффективные и сложные приложения. Для кэширования или возобновления загрузок веб-мастерам достаточно правильно настроить сервер; в некоторых средах настроить корректные ETag может быть непросто. После этого браузер будет отправлять необходимые условные запросы.
Для механизмов блокировки всё наоборот: веб-разработчикам нужно отправлять запросы с правильными заголовками, а веб-мастера могут в основном полагаться на приложение, которое выполняет необходимые проверки.
В обоих случаях ясно одно: условные запросы — фундаментальная возможность, лежащая в основе Всемирной паутины.
См. также
304 Not ModifiedIf-None-Match-
Apache Server
mod_deflate.cизменяет ETag при сжатии
© 2005–2025 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Conditional_requests