Как узнать, когда отправлять ответ 304 Not Modified

Я пишу метод обработки ресурсов, в котором я контролирую доступ к различным файлам, и я хотел бы иметь возможность использовать кеш браузера. У меня двоякий вопрос:

  1. Какие окончательные HTTP-заголовки мне нужно проверить, чтобы точно знать, следует ли мне отправлять ответ 304, и что я ищу, когда проверяю их?

  2. Кроме того, есть ли какие-либо заголовки, которые мне нужно отправить при первоначальной отправке файла (например, «Last-Modified») в качестве ответа 200?

Какой-нибудь псевдокод, вероятно, был бы наиболее полезным ответом.


А что насчет заголовка управления кешем? Могут ли различные возможные значения этого влиять на то, что вы отправляете клиенту (а именно, max-age), или должны выполняться только if-modified-Since?

Ответов (5)

Решение

Вот как я это реализовал. Код работает чуть больше года и с несколькими браузерами, поэтому я считаю его довольно надежным. Это основано на RFC 2616 и на наблюдении за тем, что и когда отправляли различные браузеры.

Вот псевдокод:

server_etag = gen_etag_for_this_file (мой файл)
etag_from_browser = get_header ("Etag")

если etag_from_browser не существует:
    etag_from_browser = get_header («Если нет совпадений»)
если браузер процитировал etag:
    удалить кавычки (например, "foo" -> foo)

установить server_etag в заголовок http

если etag_from_browser соответствует server_etag
    отправить 304 код возврата в браузер

Вот фрагмент моей серверной логики, которая справляется с этим.

/ * клиент должен установить либо Etag, либо If-None-Match * /
/ * некоторые клиенты цитируют parm, убираем кавычки, если это так * /
mketag (etag, & sb);

etagin = apr_table_get (r-> headers_in, "Etag");
если (etagin == NULL)
    etagin = apr_table_get (r-> headers_in, «Если нет совпадений»);
if (etag! = NULL && etag [0] == '"') {
    int sl; 
    sl = strlen (etag);
    memmove (etag, etag + 1, sl + 1);
    etag [sl-2] = 0;
    logit (2, "etag =:% s:", etag);
}   
... 
apr_table_add (r-> headers_out, "ETag", etag);
... 
if (etagin! = NULL && strcmp (etagin, etag) == 0) {
    / * если etag совпадает, мы возвращаем 304 * /
    rc = HTTP_NOT_MODIFIED;
}   

Если вам нужна помощь с генерацией etag, задайте еще один вопрос, и я найду код, который тоже делает это. HTH!

относительно кеш-контроля:

Вам не нужно беспокоиться о контроле кеша при раздаче, кроме установки его на разумное значение. По сути, он сообщает браузеру и другим нижестоящим объектам (например, прокси) максимальное время, которое должно пройти до тайм-аута кеширования.

Вы должны отправить 304, если клиент явно заявил, что, возможно, страница уже находится в его кеше. Это называется условным GET, который должен включать в запрос заголовок if-modified- Since.

По сути, этот заголовок запроса содержит дату, с которой клиент утверждает, что имеет кэшированную копию. Вы должны проверить, изменилось ли содержимое после этой даты, и отправить 304, если это не так.

См. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 для соответствующего раздела в RFC.

Ответ 304 Not Modified может быть результатом запроса GET или HEAD с заголовком If-Modified-Since («IMS») или If-Not-Match («INM»).

Чтобы решить, что делать при получении этих заголовков, представьте, что вы обрабатываете запрос GET без этих условных заголовков. Определите, какими будут значения ваших заголовков ETag и Last-Modified в этом ответе, и используйте их для принятия решения. Надеюсь, вы построили свою систему так, что ее определение обходится дешевле, чем построение полного ответа.

Если есть INM и значение этого заголовка совпадает со значением, которое вы поместили бы в ETag, тогда ответьте 304.

Если есть IMS и значение даты в этом заголовке позже, чем то, которое вы поместили бы в Last-Modified, тогда ответьте 304.

В противном случае действуйте так, как если бы запрос не содержал этих заголовков.

Чтобы решить часть 2 вашего вопроса с наименьшими усилиями, выясните, какой из заголовков (Expires, ETag и Last-Modified) вы можете легко и правильно создать в своем веб-приложении.

Предлагаемые материалы для чтения:

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

http://www.mnot.net/cache_docs/

Мы также обрабатываем кэшированные, но защищенные ресурсы. Если вы отправляете / генерируете заголовок ETAg (который СЛЕДУЕТ вам в разделе 13.3 RFC 2616), то клиент ДОЛЖЕН использовать его в условном запросе (обычно в заголовке If-None-Match - HTTP_IF_NONE_MATCH -). Если вы отправляете заголовок Last-Modified (опять же, ОБЯЗАТЕЛЬНО), вы должны проверить заголовок If-Modified-Since - HTTP_IF_MODIFIED_SINCE -. Если вы отправляете оба, то клиент ДОЛЖЕН отправить оба, но он ДОЛЖЕН отправить ETag. Также обратите внимание, что валидация просто определяется как проверка условных заголовков на строгое равенство с теми, которые вы отправляете. Кроме того, только сильный валидатор (такой как ETag) будет использоваться для ранжированных запросов (когда запрашивается только часть ресурса).

На практике, поскольку ресурсы, которые мы защищаем, довольно статичны и допустима задержка в одну секунду, мы делаем следующее:

  1.  Проверьте, авторизован ли пользователь для доступа к запрошенному ресурсу.

         Если это не так, перенаправьте их или отправьте ответ 4xx в зависимости от ситуации. Мы сгенерируем 404 ответа на запросы, которые выглядят как попытки взлома или явные попытки выполнить завершающий запуск безопасности.

  2.  Сравните заголовок If-Modified-Since с заголовком Last-Modified, который мы отправили бы (см. Ниже) для строгого равенства.

         Если они совпадают, отправьте ответ 304 Not Modified и завершите обработку страницы.

  3.  Создайте заголовок Last-Modified, используя время модификации запрошенного ресурса

        Найдите формат даты HTTP в RFC 2616

  4.  Отправьте заголовок и содержимое ресурса вместе с соответствующим Content-Type

Мы решили отказаться от заголовка ETag, поскольку он является излишним для наших целей. Я полагаю, мы могли бы просто использовать метку даты и времени в качестве ETag. Если мы перейдем к настоящей системе ETag, мы, вероятно, будем хранить вычисленные хэши для ресурсов и использовать их как ETag.

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