【问题标题】:Why browser does not send "If-None-Match" header?为什么浏览器不发送“If-None-Match”标头?
【发布时间】:2013-04-09 11:23:42
【问题描述】:

我正在尝试在 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 进行测试,但都无法发送标头。

【问题讨论】:

  • Last-Modified: Tue, 18 Dec 2012 …Expires: Thu, 19 Nov 1981 08:52:00 GMT 有点矛盾,你不觉得吗?
  • 那是因为我想确保它没有被缓存在浏览器中。我刚刚将Expires 设置为等于Last-Modified 并得到了相同的结果。
  • 在你问题的一开始你说你想要缓存,现在你不想要了吗?
  • 抱歉,但在我看来,您似乎只是在混合您能想到的任何类型的标题,没有任何逻辑。你说Cache-Control: no-store, no-cache——但期望缓存发生?
  • 这真的很愚蠢,但我只花了 4 个小时在我的 .NET Web Api 中使用不同的方法来实现它,只是为了意识到我的 chrome 开发工具禁用了缓存。确保关闭此功能以便在 Chrome 中进行测试!!!

标签: http-headers browser-cache cache-control


【解决方案1】:

同样的问题,类似的解决方案

我一直在尝试确定为什么 Google Chrome 在访问我正在开发的网站时不会发送 If-None-Match 标头。 (Chrome 46.0.2490.71 m,虽然我不确定版本的相关性。)

这与最终引用的 OP(在关于已接受答案的评论中)不同——尽管非常相似——答案,但它解决了相同的问题:

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

先决条件

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

第一次顿悟:If-None-Match 首先需要来自服务器的 ETag

我很快意识到 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 标头!

第二个顿悟:压缩响应会改变其哈希

然后我像一袋砖头一样打我:通过在 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;

我思考为什么 NGINX 可能会在指定“gzippable”内容类型时删除我在 PHP 中发送的ETag,并想出了一个现在显而易见的答案:因为 NGINX 修改了 PHP 传回的响应正文当 NGINX 压缩它时!这很有意义;当ETag 与用于生成它的响应不匹配时,发送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。这是 根据规范,因为非压缩响应不是逐字节的 与压缩后的响应相当。

但是,NGINX 在这方面的行为可能会被认为在同一规范中存在轻微缺陷

...还说有一种东西叫做weak Etags(一个Etag值 以 W/) 为前缀,并告诉我们它可用于检查是否有响应 在语义上是等价的。在这种情况下,Nginx 不应该乱用 它。不幸的是,该检查从未进入源代码树[可悲的是,引文现在充满了垃圾邮件]。”

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

那么,解决方案是什么?

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

ob_start('ob_gzhandler');

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

其他经验教训

以下是从我的研究中收集到的一些有趣的花絮。在尝试测试服务器端缓存实现(无论是 PHP 还是其他语言)时,此信息非常方便。

Chrome 及其开发者工具“网络”面板的行为不同取决于发起请求的方式

如果请求是“新鲜的”,例如,通过按 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)回应)。

这段经历对我来说很有启发性,我希望其他人将来能找到这种价值分析。

【讨论】:

  • 不错的发现,我检查了禁用缓存框,发现总是有一个Cache-Control: no-cache标头,最后发现问题是那个小复选框.... sigh
  • 感谢您的回复!我还花了一些时间找出为什么 ETag 不起作用。正如你所说,我启用了 gzipping,这会影响我的缓存。干杯!
  • > 当 ETag 与用于生成它的响应不匹配时,发送 ETag 毫无意义我不太明白这一点......只是因为 nginx gzip 压缩了内容,为什么不发送etag 吗?没有关于如何生成 etag 的规范 - 它们是不透明的。浏览器只会在 if-none-match 中直接将其发送回 nginx 将通过它发送到应用程序(无论如何,nginx 不知道应用程序如何生成 etags)。而且,应用程序可以继续使用它的常规逻辑来查看 etags 是否匹配。
  • @TomLianza 我刚刚添加了一个解释。具有相同 ETag 的两个响应应该是相同的,逐个字节,并且 gzipping 引入了会影响任何此类比较的熵。您描述的场景是指“弱”(AKA“语义等价”)验证,NGINX 在剥离 ETag 时不考虑。套用 NGINX 开发人员 Maxim Dounin,他解释了为什么在 NGINX 论坛帖子的底部,我在我对答案的更新中引用:弱 ETag 在这种情况下实现起来很痛苦,并且相关的缓存功能仍然在这里与 Last-修改缓存验证器。
【解决方案2】:

您的响应标头包括Cache-Control: no-store, no-cache;这些会阻止缓存。

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

我会坚持单独使用Last-Modified(如果可以单独使用此标准检测到您的资源更改)-ETag 处理起来更复杂(尤其是如果您想在 PHP 中处理它自己编写脚本),Google PageSpeed/YSlow 也建议不要这样做。

【讨论】:

  • 似乎由于一些 PHP 升级Cache-Control 标头在我不知情的情况下自动生成。我使用 PHP 的 header_remove 来阻止任何不需要的标头,然后再开始发送自己的标头。非常感谢。
  • 对于遇到此问题的其他人,即使我在删除 CacheControl 后重新启动了 nginx,chrome 仍然不尊重 Etags。我必须关闭浏览器选项卡并重新打开它才能正常工作。
【解决方案3】:

为未来的我发布这个......

我遇到了类似的问题,我在响应中发送了 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 负责此逻辑。

【讨论】:

    【解决方案4】:

    类似的问题

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

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

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

    【讨论】:

      【解决方案5】:

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

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

      解决了问题。

      【讨论】:

        【解决方案6】:

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

        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”标头!

        希望对大家有所帮助

        【讨论】:

          猜你喜欢
          • 2019-08-26
          • 1970-01-01
          • 2013-08-27
          • 2012-07-18
          • 2012-11-27
          • 2011-01-07
          • 1970-01-01
          • 2022-10-31
          • 1970-01-01
          相关资源
          最近更新 更多