17

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

4

6 回答 6

40

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

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

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

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

先决条件

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

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

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

这导致我查看我的服务器端逻辑 - 特别是当我希望用户代理缓存响应时如何发送标头 - 以确定由于什么原因ETag没有发送标头以响应 Chrome 的第一个请求资源。我已经计算出将ETag标头包含在我的应用程序逻辑中的努力。

我碰巧在使用 PHP,所以@Mehran(OP)的评论突然出现在我身上(他/她说header_remove()在发送所需的与缓存相关的标头之前调用可以解决问题)。

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

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

所以,没有什么可失去的,我试着header_remove();在发送我的自定义标题之前打电话。令我惊讶的是,PHPETag突然开始发送标头!

第二个顿悟:压缩响应会改变它的哈希值

然后我像一袋砖头一样打我:通过Content-type在 PHP 中指定标头,我告诉 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 可能会ETag在指定“gzippable”内容类型时删除我在 PHP 中发送的内容,并想出了一个现在显而易见的答案:因为 NGINX 修改了当 NGINX 压缩它时 PHP 传回的响应正文!这很有意义;ETag当它与用于生成它的响应不匹配时,发送它是没有意义的。NGINX 如此智能地处理这种情况是非常巧妙的。

我不知道 NGINX 是否一直足够聪明,不会压缩未压缩但包含ETagheaders的响应主体,但这似乎就是这里发生的事情。

更新:我找到了解释 NGINX 在这方面的行为的评论,这反过来又引用了关于这个主题的两个有价值的讨论:

  1. NGINX 论坛线程讨论该行为
  2. 项目存储库中相关的讨论;见评论Posted on Jun 15, 2013 by Massive Bird

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

Nginx 在动态压缩响应时会剥离 Etag。这是根据规范,因为非压缩响应与压缩响应不是逐字节可比的。

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

... 还说有一种叫做弱 Etags 的东西(一个以 W/ 为前缀的 Etag 值),并告诉我们它可以用来检查响应是否在语义上等价。在这种情况下,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-cache和标头,而实际上Chrome 正在从其缓存中获取响应(如响应中所证明的那样)。Pragma: no-cache200 OK (from cache)

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

于 2015-10-17T18:57:32.243 回答
18

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

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

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

于 2013-04-09T13:19:18.247 回答
5

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

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

原来我http://localhost:9000用于开发(没有使用If-None-Match) - 通过切换到http://127.0.0.1:9000Chrome 1自动开始If-None-Match再次发送请求中的标头。

此外 - 确保Devtools > Network > Disable Cache [ ]未选中。

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

1我在任何地方都找不到这个记录 - 我假设 Chrome 负责这个逻辑。

chrome 开发工具说明

于 2019-01-19T12:43:44.913 回答
0

类似问题

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

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

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

于 2017-09-20T21:36:30.757 回答
0

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

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

修复解决了问题。

于 2019-03-16T04:08:43.447 回答
0

由于两个原因,这发生在我身上:

  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-03-16T20:41:23.733 回答