Xpoint
   [напомнить пароль]

GET-запрос с условием (Conditional GET)

Conditional GET (или GET с условием) — это очень полезная возможность HTTP-протокола, позволяющая заметно сэкономить трафик и нагрузку, как клиенту, так и серверу. Клиент в GET-запросе уточняет, что хочет получить страницу только если она была изменена с момента последнего обращения клиента. Если страница была изменена, то сервер возвращает её, в противном случае он выдаёт специальный HTTP-ответ “304 Not Modified”, означающий, что страница не изменилась.

Оглавление

С точки зрения клиента

Для того, чтобы клиент мог послать GET-запрос с условием, сервер должен выдавать страницы хотя бы с одним из следующих HTTP-заголовков: Last-Modified и ETag. При загрузке страницы клиент запоминает значения этих заголовков. В простейшем случае клиенту не обязательно каким-то образом их интерпретировать, достаточно просто запомнить, хотя возможна и более сложная логика работы с кэшем.

При следующем обращении к серверу, клиент включает в GET-запрос один из или оба заголовка:
If-Modified-Since — со значением, полученным ранее из Last-Modified
If-None-Match — со значением, полученным ранее из ETag.

С точки зрения сервера

Как было сказано выше, сервер должен выдавать страницы с заголовками Last-Modified и/или ETag. Соответственно, сервер должен уметь интерпретировать заголовки If-Modified-Since и/или If-None-Match.

Last-Modified и If-Modified-Since оперируют временем, когда данный документ был в последний раз изменён. Это может быть время последнего изменения для статического файла или временем генерации последней новости для страницы со списком новостей (или, например, для RSS-канала). Сервер, получив запрос с If-Modified-Since, должен проверить, не изменился ли с того момента запрашиваемый документ, и, если он НЕ изменился, выдать ответ “304 Not Modified” (без самого документа), а если изменился — выдать сам документ со стандартным кодом “200 OK”.

ETag и If-None-Match используют так называемый Entity Tag — некую уникальную строку, характеризующая данную версию запрашиваемого документа. Entity Tag может быть «строгим» (два документа имеют одинаковые ETags только если они совпадают побитово) или «нестрогим» (два документа имеют одинаковые ETags если они совпадают по содержанию, но могут отличаться в незначительных деталях). Для файла «строгим» ETag-ом может быть, например, его md5-хэш, а для динамической страницы «нестрогим» ETag-ом может быть md5-хэш её основного содержимого (без учёта дизайна и баннеров). Строгий ETag-заголовок имеет формат "строка", а нестрогий W/"строка", где строка (без кавычек) — собственно Entity Tag. Сервер, получив запрос с If-None-Match, должен проверить совпадает ли текущий ETag документа с запрошенным, и, если он НЕ изменился, выдать ответ “304 Not Modified”, а если изменился — выдать сам документ со стандартным кодом “200 OK”.

Где применяется?

  • RSS — RSS-reader периодически запрашивает RSS-feed, и ответ сервера имеет смысл только в том случае, если документ действительно был изменен. В случае RSS, кроме выдачи “304 Not Modified”, скрипт может использовать дату, присланую клиентом в заголовке If-Modified-Since, и выдать только те элементы, которые были созданы или изменились после этой даты — таким образом будет сэкономлен трафик и в том случае, что данные изменились.
  • Веб-камера, обновляющаяся картинка — периодически меняющаяся картинка (новый кадр веб-камеры, обновленный статистический график) может выдавать “304 Not Modified” до тех пор, пока не будут доступны новые данные.
  • Изображения, отдающиеся клиенту при помощи скрипта — все браузеры стараются по возможности не загружать лишний раз изображения (используя Conditional GET), поэтому имеет смысл им в этом помочь.
  • Обычные веб-страницы, генерируемые скриптом — к примеру, пользователь может несколько раз в минуту обновлять страницу форума, чтобы увидеть новые сообщения. Если их нет, то есть страница не изменилась, можно отдавать “304 Not Modified” и экономить трафик пользователя.
  • ... (впишите ваши варианты — чем больше, тем лучше)

См. также

Комментарии

2005-04-04 11:51:45 [обр] Андрей Новиков[досье]
Mozilla неправильно реагирует на страницы с Last-Modified, но без ETag (bug 181477), так что лучше ставить оба заголовка.
спустя 1 час 49 минут [обр] Дмитрий Кучкин[досье]
Может в "С точки зрения сервера" стоит упомянуть про If-None-Match: * и If-None-Match: "строка1", "строка2", "строкаN"? А также про проверку If-Modified-Since на корректность — если If-Modified-Since > time(), нужно отдать документ с "200 OK"
И хорошо бы расписать, как должен отвечать сервер при различных сочетаниях заголовков If-None-Match и If-Modified-Since. Я когда изучал RFC2616(ietf), нарисовал таблицу :) Могу запостить.
спустя 6 часов [обр] Дмитрий Кучкин[досье]

Не пропадать же добру :)
Следующий текст полностью основан на моем понимании или недопонимании :) RFC2616(ietf).

Таблица принятия сервером решения о выдаче либо "200 OK", либо "304 Not Modified" в зависимости от сочетаний принятых заголовков If-None-Match и If-Modified-Since.

If-Modified-SinceIf-None-Match
not exists(eq ETag) or (eq '*')ne ETag
not exists200304200304200304
>= Last-Modified200304200304200304
(< Last-Modified) or (> time)200304200304 (a)200304

Легенда:

200- безоговорочное соответствие (unconditionally compliant) RFC2616(ietf)
200- условное соответствие (conditionally compliant) RFC2616(ietf)
200- несоответствие (not compliant) RFC2616(ietf)
(a)- единственный случай, когда поведение apache не удовлетворяет строгому соответствию RFC2616(ietf). Очевидно, потому, что apache формирует ETag, в который составной частью входит значение Last-Modified, поэтому решение о выдаче документа принимается apache'м исключительно на основании значения If-None-Match, если данное поле существует.

Псевдокод на основе таблицы (без учета того, что значением If-None-Match может быть список)

if (
    (undefined($If-None-Match) and undefined($If-Modified-Since)) or
    (defined($If-None-Match) and ($If-None-Match != $ETag) and ($If-None-Match != '*')) or
    (defined($If-Modified-Since) and (($If-Modified-Since < $Last_Modified) or ($If-Modified-Since > time())))
   )
 then
   // 200 OK
 else
   // 304 Not Modified
fi
спустя 3 часа 42 минуты [обр] Владимир Палант[досье]

С безоговорочными соответствиями согласен. Что касается условных соответствий, то сервер всегда имеет право вернуть код 200 — стандарт говорит лишь о случаях, когда можно вернуть и код 304. В случае же, когда If-Modified-Since меньше даты последнего изменения, выдать 304 нельзя никак — не понимаю, откуда вы взяли там условное соответствие. Apache ведёт себя в этом случае не совсем корректно (" An HTTP/1.1 origin server [...] MUST NOT return a response status of 304 (Not Modified) unless doing so is consistent with all of the conditional header fields in the request."), хоть это и не играет никакой роли на практике. ИМХО, алгоритм намного проще:

if (
    $ETag->consistent($If-Match, $If-None-Match) and
    $Last-Modified->consistent($If-Modified-Since, $If-Unmodified-Since)
   )
  then
    // 300 Not Modified
  else
    // 200 OK
fi

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

спустя 1 час 22 минуты [обр] Дмитрий Кучкин[досье]

Да вот, apache меня и сбил, зараза :) На бумажке 304 в этой ячейке вначале было перечеркнуто, т.е. not compliant, пока не проверил, что apache творит. Не поднялась у меня рука заявить, что apache not compliant :)
А все ответы 200 тоже вначале были обозначены либо как unconditionally compliant, либо как conditionally compliant. Но потом меня смутила эта фраза

If any of the entity tags match the entity tag of the entity that would have been returned in the response to a similar GET request (without the If-None-Match header) on that resource, or if "*" is given and any current entity exists for that resource, then the server MUST NOT perform the requested method, unless required to do so because the resource's modification date fails to match that supplied in an If-Modified-Since header field in the request.

А следом идет

Instead, if the request method was GET or HEAD, the server SHOULD respond with a 304 (Not Modified) response, ...

Вроде бы MUST NOT perform the requested method, но SHOULD respond with a 304. Запрос удовлетворять нельзя однозначно? Но 304 отдавать всего-то настоятельно рекомендуется? Смысл ускользает. Или MUST NOT относится ко всему, кроме GET/HEAD? Я в таких длинных фразах запутываюсь :)

Да, по поводу конкретной реализации метода consistent. Как лучше поступать, если в If-None-Match приехало невалидное значение, типа "строка1""строка2" (без разделителей) или *zxcv? Вначале я такие заголовки отбрасывал, а потом посмотрел на тот же apache, который съедает все, кроме полной непотребщины, и вспомнил, что где-то читал, что принцип должен быть таким: принимать все, что только возможно, но отвечать строго по стандарту. Где читал, уже не помню.

P.S. А как в Вашем примере отрабатывать ситуацию отсутствия заголовков? Если метод возвратит true, то это корректно, если только имеется другой заголовок, но если отсутствуют оба, то при true ответ 304 будет not compliant. И, соответственно, все наоборот, если возвращать false.

спустя 28 минут [обр] Дмитрий Кучкин[досье]
P.P.S. Как у меня оказалось в первой колонке 304 я уже сам не понимаю :)
Вот самый первый вариант таблицы — после первого прочтения RFC2616, без тщательного вчитывания, и без оглядки на apache. По-моему, как раз совпадает с Вашими замечаниями :)
If-Modified-SinceIf-None-Match
not exists(eq ETag) or (eq '*')ne ETag
not exists200304200304200304
>= Last-Modified200304200304200304
(< Last-Modified) or (> time)200304200304200304
спустя 1 час 11 минут [обр] Владимир Палант[досье]

На всякий случай заглянул в RFC2119(ietf) — вы правы и в этой фразе действительно противоречие. Я так понимаю, категорическое "MUST NOT" на самом деле не относится к запросам типа HEAD и GET. К примеру, для запросов типа POST это правило действительно имеет смысл. Но для таких запросов крайне редко делается поддержка If-None-Match, здесь о них речь вообще не идет.

Ситуацию полного отсуствия заголовков надо добавить, я это действительно yпустил:

if (
    exists_at_least_one($If-Match, $If-None-Match,
                        $If-Modified-Since, $If-Unmodified-Since) and
    $ETag->consistent($If-Match, $If-None-Match) and
    $Last-Modified->consistent($If-Modified-Since, $If-Unmodified-Since)
   )
  then
    // 300 Not Modified
  else
    // 200 OK
fi

Принимать несоответствующие стандарту значения ИМХО надо только тогда, когда это не требует дополнительных усилий. Шанс напороться на клиента, который пошлёт некорректный запрос именно такого вида, очень невелик — разве что вы знаете конкретную глючную реализацию и именно под неё подстраиваетесь.

С таблицей в таком виде согласен.

спустя 5 месяцев [обр] Co[досье]
а зависит ли посылка/не посылка браузером If-Modified-Since, If-None-Match от заголовка Cache-control?
спустя 1 год 1 месяц [обр] пользователь удален
Прежде всего необходимо проверить, не выставлен ли ранее статус, отличный от 200:

If the request would normally result in anything other than a 200 (OK) status, or if the passed If-Modified-Since date is invalid, the response is exactly the same as for a normal GET. A date which is later than the server's current time is invalid.

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

Powered by POEM™ Engine Copyright © 2002-2005