同样的问题,相似的解决方案
我一直在试图确定为什么Google Chrome在访问我正在开发的网站时不会发送If-None-Match
头信息。(Chrome 46.0.2490.71 m,尽管我不确定版本是否相关。)
这个答案与OP最终引用的(在评论中关于接受的答案),虽然有所不同,但它解决了同样的问题:
当服务器端逻辑通过PHP或类似方式发送ETag
或Last-Modified
头信息时,浏览器在后续请求中不会“按照应该的方式”发送If-None-Match
头信息。
先决条件
使用自签名TLS证书会在Chrome中将锁变成红色,改变Chrome的缓存行为。在尝试解决这种问题之前,请按https://dev59.com/z3fZa4cB1Zd3GeqPRnB2#19102293中的说明将自签名证书安装到有效的受信任根存储区,并完全重启浏览器。
第一个顿悟:If-None-Match首先需要来自服务器的ETag
我很快意识到Chrome(以及可能大多数或所有其他浏览器)在服务器已经响应先前请求并发送ETag标头之前,不会发送If-None-Match标头。从逻辑上讲,这是完全合理的;毕竟,Chrome怎么能发送未给定值的If-None-Match呢?
这促使我查看我的服务器端逻辑——特别是当我希望用户代理缓存响应时如何发送标头——以确定为什么第一次请求资源时没有发送ETag标头。我曾经努力在我的应用程序逻辑中包含ETag标头。
我碰巧使用PHP,所以@Mehran(OP)的评论引起了我的注意(他/她说在发送所需的缓存相关标头之前调用header_remove()可以解决问题)。
坦白说,我对这个解决方案持怀疑态度,因为a)我相当确定PHP默认情况下不会发送任何头文件(根据我的配置它确实不会); b)当我在设置自定义缓存头之前调用
var_dump(headers_list());
时,唯一设置的头是我故意设置的一个头文件。
header('Content-type: application/javascript; charset=utf-8');
所以,没有什么好失去的,我尝试在发送自定义标头之前调用
header_remove();
。令我惊讶的是,PHP突然开始发送
ETag
标头!
第二个顿悟:压缩响应会改变其哈希值
然后我像被一袋砖头击中一样,意识到通过在PHP中指定
Content-type
标头,我告诉NGINX(我使用的Web服务器)将响应GZIP一次,一旦PHP将其交还给NGINX!要明确的是,我指定的
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在指定“可gzip压缩的”内容类型时会删除我通过PHP发送的
ETag
,现在得到了一个很明显的答案:因为NGINX修改了PHP返回的响应正文当NGINX对其进行gzip压缩时!这完全是有道理的;如果不会匹配用于生成它的响应,则发送
ETag
没有意义。NGINX如此聪明地处理这种情况真是太巧妙了。
我不知道NGINX是否一直足够聪明,不会压缩响应主体,但包含未压缩的
ETag
头信息,但这似乎就是这里发生的情况。
更新:我找到了
解释NGINX在这方面行为的评论,其中引用了两个关于此主题的有价值的讨论。
- NGINX论坛讨论该行为的帖子。
- 在项目仓库中有类似的讨论;请查看评论
由Massive Bird于2013年6月15日发布
。
为了保留这个有价值的解释,如果它不幸消失了,我引用Massive Bird
在讨论中的贡献:
Nginx在即时压缩响应时会剥离ETag。 这是根据规范的要求,因为未经压缩的响应与经过压缩的响应不能按字节逐一比较。
然而,NGINX在此方面的行为可能被认为略有缺陷,因为同一规范
“... 还提到了一种称为弱 Etag 的东西(以 W/ 开头的 Etag 值),并告诉我们它可以用于检查响应是否语义等效。在这种情况下,Nginx 不应该对其进行处理。不幸的是,这个检查从未被纳入源代码树中 [引文现在充斥着垃圾邮件]。”
“我不确定 NGINX 在这方面的当前态度,特别是它是否已经添加了对“弱”Etag 的支持。”
“那么,有什么解决办法呢?如何将 ETag 返回到响应中?在 PHP 中进行 gzip 压缩,这样 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地址栏中输入URL并按下回车键发起的,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)
响应中所示)。
这次经历对我来说相当有启发性,我希望其他人在未来也能从这个分析中获得价值。