Почему браузер не отправляет заголовок "If-None-Match"?

Я пытаюсь загрузить (и, надеюсь, кэш) динамически загруженное изображение в PHP. Вот отправленные и полученные заголовки:

Запрос:

GET /url:resource/Pomegranate/resources/images/logo.png HTTP/1.1
Host: pome.local
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.22 (KHTML, like Gecko) Ubuntu Chromium/25.0.1364.160 Chrome/25.0.1364.160 Safari/537.22
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: PHPSESSID=fb8ghv9ti6v5s3ekkmvtacr9u5

Ответ:

HTTP/1.1 200 OK
Date: Tue, 09 Apr 2013 11:00:36 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.14 ZendServer/5.0
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Disposition: inline; filename="logo"
ETag: "1355829295"
Last-Modified: Tue, 18 Dec 2012 14:44:55 Asia/Tehran
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: image/png

Когда я перезагружаю URL-адрес, то точно такие же заголовки отправляются и принимаются. Мой вопрос в том, что я должен отправить в свой ответ, чтобы увидеть заголовок If-None-Match в последующем запросе?

ПРИМЕЧАНИЕ. Я считаю, что эти заголовки делались совсем недавно, хотя я не могу быть уверен, но я думаю, что браузеры изменились, чтобы больше не отправлять заголовок If-None-Match (я видел этот заголовок). Я тестирую Chrome и Firefox, и оба не могут отправить заголовок.

Ответ 1

Вы говорите Cache-Control: no-store, no-cache - но ожидаете, что кэширование произойдет?

Удалите эти значения (я думаю, что must-revalidate, post-check=0, pre-check=0 может/должен быть сохранен - ​​они сообщают обозревателю проверить с сервером, произошли ли изменения).

И я бы придерживался только Last-Modified (если изменения в ваших ресурсах могут быть обнаружены только с использованием этого критерия) - ETag - более сложная вещь для обработки (особенно если вы хотите иметь дело с ней в своем PHP скрипт), и Google PageSpeed ​​/YSlow также советуют против этого.

Ответ 2

Та же проблема, аналогичное решение

Я пытался определить, почему Google Chrome не будет отправлять заголовки If-None-Match при посещении сайта, который я разрабатываю. (Chrome 46.0.2490.71 м, хотя я не уверен, насколько актуальна версия.)

Это другой, хотя и очень похожий, ответ, чем в конечном счете цитируемый ФП (в комментарии относительно принятого ответа), но он решает ту же проблему:

Браузер не отправляет заголовок If-None-Match в последующих запросах "когда должен" (т. ETag Логика на стороне сервера, через PHP или аналогичная, использовалась для отправки ETag или Last-Modified в первом ответе),

Предпосылки

Использование самозаверяющего сертификата TLS, который в Chrome окрашивает блокировку в красный цвет, изменяет поведение кэширования Chrome. Прежде чем пытаться устранить проблему такого рода, установите самозаверяющий сертификат в эффективном доверенном корневом хранилище и полностью перезапустите браузер, как описано на fooobar.com/questions/794485/....

1-е Богоявление: если-None-Match требует ETag от сервера, сначала

Я довольно быстро понял, что Chrome (и, возможно, большинство или все другие браузеры) не будет отправлять заголовок If-None-Match до тех пор, пока сервер не отправит заголовок ETag в ответ на предыдущий запрос. Логично, что это имеет смысл; в конце концов, как Chrome может отправлять If-None-Match когда ему никогда не присваивается значение?

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

Я использую PHP, поэтому комментарий @Mehran (OP) выскочил на меня (он говорит, что вызов header_remove() перед отправкой нужных заголовков, связанных с кэшем, решает проблему).

Откровенно говоря, я скептически отнесся к этому решению, потому что a) я был почти уверен, что PHP не будет отправлять какие-либо собственные заголовки по умолчанию (и это не так, учитывая мою конфигурацию); и б) когда я вызвал var_dump(headers_list()); незадолго до установки моих пользовательских кэширующих заголовков в PHP, единственным набором заголовков был тот, который я специально устанавливал чуть выше:

header('Content-type: application/javascript; charset=utf-8');

Итак, мне нечего терять, я попытался вызвать header_remove(); перед отправкой моих пользовательских заголовков. И, к моему большому удивлению, PHP неожиданно начал отправлять заголовок ETag !

2-е Богоявление: сжатый ответ меняет свой хэш

Затем он ударил меня, как мешок кирпичей: указав заголовок Content-type в PHP, я сказал NGINX (веб-серверу, который я использую) GZIP ответ, как только PHP вернет его обратно в NGINX! Чтобы было ясно, Content-type который я указывал, был в списке типов NGINX для gzip.

Для простоты мои настройки NGINX GZIP следующие, а PHP подключен к NGINX через php-fpm:

gzip            on;
gzip_min_length 1;
gzip_proxied    expired no-cache no-store private auth;
gzip_types      text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;

gzip_vary on;

Я подумал, почему NGINX может удалить ETag который я отправил в PHP, когда указан "тип содержимого gzippable", и придумал очевидный на данный момент ответ: потому что NGINX изменяет тело ответа, которое PHP передает обратно, когда NGINX сжимает его! Это имеет смысл; нет смысла отправлять ETag если он не совпадает с ответом, использованным для его генерации. Довольно приятно, что NGINX так разумно справляется с этим сценарием.

Я не знаю, был ли NGINX всегда достаточно умным, чтобы не сжимать тела ответов, которые были несжатыми, но содержали заголовки ETag, но, похоже, именно это и происходит здесь.

ОБНОВЛЕНИЕ: я нашел комментарий, который объясняет поведение NGINX в этом отношении, который, в свою очередь, приводит два ценных обсуждения по этому вопросу:

  1. NGINX ветка форума, обсуждающая поведение.
  2. Тангенциально связанная дискуссия в репозитории проекта; см. комментарий Posted on Jun 15, 2013 by Massive Bird.

В интересах сохранения этого ценного объяснения, если оно исчезнет, я цитирую вклад Massive Bird в дискуссию:

Nginx снимает Etag, когда получает ответ на лету. Это в соответствии со спецификацией, так как ответ без gzipped не является побайтным, сравнимым с ответом gzipped.

Тем не менее, поведение NGINX в этом отношении можно считать слегка ошибочным в том

... также говорит, что есть вещь, называемая слабыми Etags (значение Etag с префиксом W/), и говорит нам, что его можно использовать для проверки того, является ли ответ семантически эквивалентным. В этом случае Nginx не должен связываться с этим. К сожалению, эта проверка так и не попала в дерево исходных текстов (к сожалению, цитата теперь заполнена спамом). "

Я не уверен насчет текущего положения NGINX в этом отношении и, в частности, добавил ли он поддержку "слабых" Etags.

Итак, какое решение?

Итак, какое решение вернуть ETag обратно в ответ? Выполните gzipping в PHP, чтобы NGINX увидел, что ответ уже сжат, и просто передает его, оставляя заголовок ETag без изменений:

ob_start('ob_gzhandler');

Как только я добавил этот вызов перед отправкой заголовков и тела ответа, PHP начал отправлять значение ETag с каждым ответом. Да!

Другие извлеченные уроки

Вот несколько интересных моментов, почерпнутых из моих исследований. Эта информация весьма полезна при попытке протестировать реализацию кэширования на стороне сервера, будь то на PHP или другом языке.

Chrome и его панель инструментов разработчика "Net" ведут себя по-разному в зависимости от того, как инициируется запрос.

Если запрос "сделан заново", например, нажатием Ctrl+F5, Chrome отправляет следующие заголовки:

Cache-Control: no-cache
Pragma: no-cache

и сервер отвечает 200 OK.

Если запрос сделан только с F5, Chrome отправляет эти заголовки:

Pragma: no-cache

и сервер отвечает 304 Not Modified.

И наконец, если запрос сделан путем нажатия на ссылку на страницу, которую вы уже просматриваете, или размещения фокуса в адресной строке Chrome и нажатия Enter, Chrome отправляет следующие заголовки:

Cache-Control: no-cache
Pragma: no-cache

и сервер отвечает 200 OK (from cache).

Хотя это поведение сначала немного сбивает с толку, но если вы не знаете, как оно работает, это идеальное поведение, потому что оно позволяет очень тщательно протестировать каждый возможный сценарий запроса/ответа.

Возможно, наиболее запутанным является то, что Chrome автоматически вставляет заголовки Cache-Control: no-cache и Pragma: no-cache в исходящий запрос, когда фактически Chrome получает ответы из своего кеша (о чем свидетельствует ответ 200 OK (from cache)).

Этот опыт был довольно информативным для меня, и я надеюсь, что другие найдут этот анализ ценности в будущем.

Ответ 3

Аналогичная проблема

Я пытался получить условный запрос GET с заголовком If-None-Match, поставив соответствующий заголовок Etag, но безуспешно в любом браузере, который я пробовал.

После большого количества проб я понимаю, что браузеры обрабатывают как GET, так и POST по тому же пути, что и один и тот же кандидат на кеш. Таким образом, наличие GET с правильным Etag было эффективно отменено с немедленным "POST" на тот же путь с Cache-Control:"no-cache, private", хотя он был поставлен X-Requested-With:"XMLHttpRequest".

Надеюсь, это может быть полезно кому-то.

Ответ 4

Отправляя это для будущего меня...

У меня была похожая проблема, я отправлял ETag в ответе, но HTTP-клиент не отправлял заголовок If-None-Match в последующих запросах (что было странно, потому что это было накануне).

Оказывается, я использовал http://localhost:9000 для разработки (в которой не использовался If-None-Match) - при переключении на http://127.0.0.1:9000 Chrome 1 автоматически начал отправлять If-None-Match Заголовок в запросах снова.

Дополнительно - убедитесь, что Devtools > Network > Disable Cache [ ] не отмечен.

Chrome: версия 71.0.3578.98 (официальная сборка) (64-разрядная версия)

1 Я нигде не могу найти это задокументировано - я предполагаю, что Chrome был ответственен за эту логику.