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