为什么浏览器不发送“If-None-Match”标题?

问题描述 投票:15回答:6

我正在尝试下载(并希望缓存)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进行测试,但都无法发送标题。

http-headers browser-cache cache-control
6个回答
18
投票

您的回复标题包括Cache-Control: no-store, no-cache;这些可以防止缓存。

删除这些值(我认为must-revalidate, post-check=0, pre-check=0可以/应该保留 - 他们告诉浏览器检查服务器是否有变化)。

我会坚持单独使用Last-Modified(如果仅使用此标准可以检测到对资源的​​更改) - ETag是一个更复杂的事情要处理(特别是如果你想在自己的PHP脚本中处理它),以及Google PageSpeed / YSlow也建议不要这样做。


32
投票

Same Problem, Similar Solution

我一直在努力确定为什么谷歌浏览器在访问我正在开发的网站时不会发送If-None-Match标头。 (Chrome 46.0.2490.71米,虽然我不确定该版本有多相关。)

这是一个不同的 - 虽然非常相似 - 答案比最终引用的OP(在关于接受的答案的评论中),但它解决了同样的问题:

浏览器不会在后续请求中“何时应该”发送If-None-Match标头(即,服务器端逻辑,通过PHP或类似方法,已用于在第一个响应中发送ETagLast-Modified标头)。

Prerequisites

使用自签名TLS证书可以改变Chrome的缓存行为,从而改变Chrome中的锁定红色。在尝试解决此类问题之前,请在有效的受信任的根存储中安装自签名证书,并完全重新启动浏览器,如https://stackoverflow.com/a/19102293中所述。

1st Epiphany: If-None-Match requires an ETag from the server, first

我很快意识到Chrome(以及可能是大多数或所有其他浏览器)都不会发送If-None-Match标头,直到服务器已经发送了ETag标头以响应先前的请求。从逻辑上讲,这是完全合理的;毕竟,当Chrome从未被赋予价值时,它如何发送If-None-Match

这让我看看我的服务器端逻辑 - 特别是当我希望用户代理缓存响应时如何发送标头 - 以确定为什么没有发送ETag标头以响应Chrome的响应第一次请求资源。我已经计划在我的应用程序逻辑中包含ETag标头。

我碰巧使用PHP,所以@ Mehran(OP的)评论跳出来了(他/她说在发送所需的缓存相关头之前调用header_remove()解决了这个问题)。

坦率地说,我对这个解决方案持怀疑态度,因为a)我非常确定PHP默认情况下不会发送任何自己的头文件(并且根据我的配置,它不会发送);和b)当我在PHP中设置自定义缓存标头之前调用var_dump(headers_list());时,唯一的标头集是我在上面有意设置的标头集:

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

所以,没有什么可失去的,我在发送自定义标题之前尝试调用header_remove();。令我惊讶的是,PHP开始突然发送ETag标题!

2nd Epiphany: gzipping the response changes its hash

然后我就像一袋砖一样击中了我:通过在PHP中指定Content-type标题,我告诉NGINX(我正在使用的网络服务器),一旦PHP将其交回NGINX,就会响应GZIP!为了清楚起见,我指定的Content-type是在NGINX的gzip类型列表中。

为了彻底,我的NGINX GZIP设置如下,PHP通过php-fpm连接到NGINX:

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;

我考虑为什么当指定“gzippable”内容类型时NGINX可能会删除我在PHP中发送的ETag,并提出了一个现在明显的答案:因为当NGINX gzips时,NGINX会修改PHP传回的响应主体!这很有道理;发送ETag没有意义,因为它不会与用于生成它的响应相匹配。 NGINX如此智能地处理这种情况非常灵活。

我不知道NGINX是否总是足够聪明,不能压缩未压缩但包含ETag标头的响应体,但这似乎就是这里发生的事情。

更新:我找到了commentary that explains NGINX's behavior in this regard,反过来引用了关于这个主题的两个有价值的讨论:

  1. NGINX forum thread discussing the behavior
  2. Tangentially-related discussion in a project repository;看到评论Posted on Jun 15, 2013 by Massive Bird

为了保留这个有价值的解释,如果它真的消失了,我引用Massive Bird对讨论的贡献:

Nginx在飞行中抓取响应时剥离了Etag。这是根据规范,因为非gzip响应不是与gzip响应相比的逐字节。

然而,NGINX在这方面的行为可能被认为是相同的规范略有缺陷

...还说有一个名为弱Etags的东西(一个以W /为前缀的Etag值),并告诉我们它可用于检查响应是否在语义上等效。在这种情况下,Nginx不应该搞乱它。不幸的是,这项检查从未进入源代码树[引用现在充斥着垃圾邮件,遗憾的是]。“

我不确定NGINX目前在这方面的处理方式,特别是它是否增加了对“弱”Etags的支持。

So, What's the Solution?

那么,将ETag重新纳入响应的解决方案是什么?在PHP中执行gzipping,以便NGINX看到响应已经被压缩,并简单地传递它,同时保持ETag头完整:

ob_start('ob_gzhandler');

一旦我在发送标头和响应主体之前添加了这个调用,PHP就开始在每个响应中发送ETag值。是!

Other Lessons Learned

以下是从我的研究中收集到的一些有趣的花絮。尝试测试服务器端缓存实现(无论是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-cachePragma: no-cache标头,而事实上Chrome正在从其缓存中获取响应(如200 OK (from cache)响应中所示)。

这段经历对我来说相当丰富,我希望其他人在未来找到这种价值分析。


1
投票

发布此信息以供将来使用...

我有一个类似的问题,我在响应中发送ETag,但HTTP客户端没有在后续请求中发送If-None-Match标头(这很奇怪,因为它是前一天)。

事实证明我使用http://localhost:9000进行开发(没有使用If-None-Match) - 通过切换到http://127.0.0.1:9000 Chrome1自动开始再次发送请求中的If-None-Match标头。

另外 - 确保Devtools > Network > Disable Cache [ ]未经检查。

Chrome:版本71.0.3578.98(官方版)(64位)

1我无法找到任何记录的内容 - 我假设Chrome负责此逻辑。


1
投票

这件事发生在我身上有两个原因:

  1. 我的服务器没有发送etag响应头。我通过添加以下内容更新了我的jetty web.xml以返回etag: <init-param> <param-name>etags</param-name> <param-value>true</param-value> </init-param>
  2. 我调用的URL是xml文件。当我将其更改为html文件时,chrome开始发送“if-none-match”标题!

我希望它对某人有帮助


0
投票

类似的问题

我试图获得带有If-None-Match标头的条件GET请求,提供了适当的Etag标头,但在我试过的任何浏览器中无济于事。

经过大量的试验,我意识到浏览器将GETPOST都视为与同一缓存候选者相同的路径。因此,GET与适当的Etag有效取消,立即“POST”到与Cache-Control:"no-cache, private"相同的路径,即使它是由X-Requested-With:"XMLHttpRequest"提供的。

希望这可能对某人有所帮助。


0
投票

这发生在我身上,因为我将缓存大小设置得太小(通过组策略)。

它没有发生在Incognito中,这让我意识到这可能就是这种情况。

修复解决了这个问题。

© www.soinside.com 2019 - 2024. All rights reserved.