在 HTTP 客户端(浏览器或者缓存服务器)上,如果某个 URL 对应的缓存过期了,客户端会再次向该 URL 发送一个条件请求(带有If-Modified-Since/If-None-Match请求头),如果服务端(缓存服务器或者源站)返回的状态码是 304(没有响应体),则客户端会根据该304响应所包含的一些响应头(DateLast-ModifiedCache-Control等)重新计算出这条缓存的过期时间,比如:

HTTP/2 304
Cache-Control: max-age=86400

这样的 304响应,就能让这条缓存重新续命一天;如果返回的状态码是 200,则整条缓存会被新返回的响应体替换掉。无论是哪种情况,这条缓存都重新变的有效了,HTTP 规范里把这一“让过期的缓存重新变的有效”过程,叫做 revalidate,英语翻译过来应该是“使重新生效”。

不过使动用法总是让人不好记忆,你也可以把 revalidate 理解成“再次校验”的意思:再次校验看看缓存是不是真的过期了,真过期了的话返回 200,假过期(客户端判断为过期了,但服务端说并没有)的话返回 304

所以现在知道了,revalidate 是个常见的动作,缓存过期就会 revalidate ,缓存过期就会 revalidate ,缓存过期就会 revalidate ,说三遍,revalidate 不需要专门的指令。

搞清楚了 revalidate 这个词的含义,那么再来推测一下 must-revalidate是用来做什么的。must-revalidate直译过来是“必须再次校验”,那是不是说每次使用缓存前都要先校验一遍?即便没有过期?于是很多人按照这样的推测,写出了如下的 Cache-Control头:

Cache-Control: max-age=86400, must-revalidate   

写这个配置的人想表示的是:该缓存有效期为一天,在这一天内,每次使用缓存前要先校验一遍才能使用。可试试就知道了,这里的must-revalidate 并不会生效,这条缓存仍然是直接读取了本地。

这是为什么呢?是因为must-revalidate生效有个前提,前提就是这个缓存必须已经过期,也就是说,必须一天以后,这个must-revalidate才可能发挥作用,规范里说的原话是:

The "must-revalidate" response directive indicates that once it has become stale, a cache MUST NOT use the response to satisfy subsequent requests without successful validation on the origin server.

翻译过来大概就是,must-revalidate指令是用来表示在一个缓存过期之后,不能直接使用这个过期的缓存,必须校验之后才能使用。哎?What?回忆一下刚才重复三遍的话:“缓存过期就会 revalidate”,revalidate 是缓存过期后自然而然的表现,怎么还需要专门的指令呢?

再细读规范就知道了,原来 must-revalidate生效的场景还有一个大前提,那就是 HTTP 规范是允许客户端在某些特殊情况下直接使用过期缓存的,比如校验请求发送失败的时候,还比如有配置一些特殊指令(stale-while-revalidatestale-if-error等)的时候,原文是这样的:

A cache MUST NOT send stale responses unless it is disconnected (i.e., it cannot contact the origin server or otherwise find a forward path) or doing so is explicitly allowed

must-revalidate的作用就是让那个“unless”失效 ,带有 must-revalidate 的缓存,在任何情况下,都必须成功 revalidate 后才能使用,没有例外。

各种缓存服务器软件,比如 NGINX、Vanish、Squid 都或多或少的允许通过Cache-Control指令或者修改软件配置的方式返回过期缓存,同时它们也都遵循了 HTTP 规范,加上must-revalidate的确能阻止返回过期缓存的行为。国内各大 CDN 厂商应该用的都是自研软件,不确定支持不支持返回过期缓存,所以 must-revalidate在国内网络环境能不能派上用场也不太确定。

更加不确定的是,很多用了 must-revalidate 指令的人真的知道它的作用是什么吗?现在回到我前面举的那个希望每次使用缓存前先校验一遍的例子:

Cache-Control: max-age=86400, must-revalidate   

写出这个配置的人其实真正想要的是Cache-Control: no-cacheCache-Control的几个指令特别容易混淆,不能望文生义。比如no-cache,并不是指不能用 cache,客户端仍会把带有 no-cache 的响应缓存下来,只不过每次不会直接用缓存,而得先 revalidate 一下,所以其实no-cache真正合适的名字才是 must-revalidate。而现在的must-revalidate更合适的名字可能是 never-return-stale。如果你想让客户端完全不缓存响应,应该用no-store,带有no-store的响应不会被缓存到任意的磁盘或者内存里,它才是真正的 no-cache

计算机领域有个名言警句:

There are only two hard problems in Computer Science: cache invalidation, and naming things.(计算机领域只有有两大难题,“让缓存失效”和“给东西命名”)

而给上面讲的这些 Cache-Control指令命名,就是在给 HTTP 缓存失效相关的东西命名,恰好是两个难题撞到一起了,难上加难,才造成了这乱糟糟的局面。

读到这里你可能已经晕了,但本文才讲了一半。我上面只讲了must-revalidate在缓存服务器上的作用,还没说在浏览器上的作用。既然我说了must-revalidate更合适的名字是 never-return-stale,那浏览器有没有 return stale 的情况呢,也就是说浏览器会不会使用过期缓存呢?

还真有,那就是浏览器的后退前进功能。当点击 back/forwrad 按钮时,浏览器会尽量用本地缓存来重新打开页面,即便缓存已经过期了,也不会 revalidate。那must-revalidate能阻止这一行为,强迫该缓存 revalidate 吗?答案是并不能,甚至no-cache也不行,只有比no-cache更强劲的no-store才可以,因为硬盘上都没有缓存,浏览器想用也没法用啊。

另外值得注意的是,如果真有上面这个需求,未来可能no-store也帮不了你,因为 Chrome 目前在实现 bfcache,如果实现了,在页面前进后退时,页面内容会直接从内存缓存里读取,页面甚至都不会重新加载,连 JS 变量都保存着上次的值。

上面三段总结一下就是,must-revalidate的本职工作在浏览器端并没有发挥作用。那是不是说浏览器们就完全就不认什么 must-revalidate,连解析都没解析它? 在 Firefox 和 Safari 里可能还真是这样的,但在 Chrome 里,不知道什么人手贱给 must-revalidate实现了一个规范里并没有要求的功能,下面我来说说到底是什么功能。

想让一个资源能缓存,有三种方式,按照解析优先级排序如下:

  1. HTTP 1.1 风格的Cache-Control 响应头中的 max-age指令
  2. HTTP 1.0 风格的 Expires 响应头
  3. Last-Modified响应头

很多人只知道前两个,还知道 1 比 2 优先级高,但不知道第 3 个,我举个例子:

HTTP/2 200
Date: Wed, 27 Mar 2019 22:00:00 GMT
Last-Modified: Wed, 27 Mar 2019 12:00:00 GMT

上面这个响应,没有Cache-Control,也没有 Expires,但它其实也可以被缓存,可缓存时长是用 Date响应头的时间减去Last-Modified的时间,得出的时长再除以10,用汉语描述的话,就是用这个文件最近一次更新到现在的十分之一时长作为可缓存时长,这个例子的话,计算出的可缓存时长是一小时。

这种可缓存时长的算法用代码表示是 (date_value - last_modified_value) * 0.10, 它是由 HTTP 规范推荐的算法,但规范中仅仅是推荐而已,并没有做强制要求,比如 Firefox 中就在这个算法的基础上还和 7 天时长取了一次最小值,是 min(one-week, (date_value - last_modified_value) * 0.10)

扯远了,我们把这第 3 种缓存方式叫做启发式的(heuristic)缓存,你可以理解是没有显示的用前两种方式设置缓存时长的话,浏览器就会用这种隐式的缓存方式。

如果你想禁用由 Last-Modified响应头造成的启发式缓存,正确的做法是要加上 Cache-Control: no-cache,但在 Chrome 中,Cache-Control: must-revalidate也有同样的功效。很多人在 Chrome 里开发和测试,所以误以为这是 must-revalidate的正规作用,从而推断其它浏览器也是支持的,但其实规范里并没有指出 must-revalidate有关闭启发式缓存的功效,其它浏览器也都不支持这种用法。Chrome 里的这行代码加于 2008 年,看注释像是 Chrome 最早的开发工程师,现在 Chrome 的 VP Darin Fisher 写的。

好了,差不多讲完了, 再来句总结:must-revalidate在缓存服务器上有一点点作用,但比较小众;在浏览器端几乎没有任何作用。绝大多数情况,人们都是把它误用为 no-cache了。或者是完全没细研究,直接把max-age=0, no-cache, no-store, must-revalidate一坨都塞进去了,反正能 work 就不管了。

如果你在真实项目中用到了 must-revalidate,不妨分享下 。本文如有错误,欢迎指正。下篇文章,我可能会讲一下 Chrome 刚刚实现的一个新的 Cache-Control指令 stale-while-revalidate(如果 Chrome 的人抢先写了话 https://developers.google.com/web/updates/2019/,我就没激情写了 )

mfc中picture control的用法_可能是最被误用的 HTTP 响应头之一 Cache-Control: must-revalidate相关推荐

  1. MFC中picture控件如何响应鼠标移动?

    MFC中picture控件如何响应鼠标移动? 2011-08-21 21:23 轻若惊鸿 | 浏览 3354 次 硬件网络 我将一个picture控件的属性中的Notify勾上了,但是只能响应鼠标按键 ...

  2. MFC中CString.Format的用法

    http://www.cnblogs.com/kongtiao/archive/2012/06/13/2548033.html 在MFC程序中,使用CString来处理字符串是一个很不错的选择.CSt ...

  3. java中include标签的用法_原 ng-include用法分析以及多标签页面的简单实现方式

    在平时的项目开发中,应该会经常遇到上图所示的需求,就是在一个页面中有多个标签,被选中的标签颜色会高亮显示,切换不同标签显示相应的不同内容.如果内容代码过多则写在同一个html文件就会显得特别乱,所以这 ...

  4. java中常量final的用法_详解Java中final的用法

    本文主要介绍了Java中final的使用方法,final是java的关键字,本文就详细说明一下它的使用方法,需要的朋友可以参考下 概念 final 具有"不可改变的"的含义,可以修 ...

  5. python中while语句的用法_全面解析Python的While循环语句的使用方法

    Python 编程中 while 语句用于循环执行程序,即在某条件下,循环执行某段程序,以处理需要重复处理的相同任务.其基本形式为: while 判断条件: 执行语句-- 执行语句可以是单个语句或语句 ...

  6. matlab中ode45函数的用法_带你理解Excel中COUNTIF函数的简单用法

    每天5分钟,每天学一点. COUNTIF函数是Excel中最常用的统计函数之一,它的作用主要是用于根据特定条件对数据进行统计.假如,你想统计一下本周总共做了几次健身/瑜伽,或者统计上了几次培训课,那么 ...

  7. python中叹号的用法_感叹号

    感叹号: 感叹号,为标点符号的一种,又称惊叹号.叹号,用于句子结尾,表示惊讶.主要用在感叹句的句末,表示强烈的感情.感叹句是以抒发感情为主的句子,它所表示的感情有赞颂.喜悦.愤怒.叹息.惊讶,伤悼等. ...

  8. 简述java中的注释以及用法_怎样理解 Java 注解和运用注解编程?

    正好最近在公众号(BetterAndroid)发了一篇关于注解的文章,贴在这里吧,希望对题主有帮助. 一.什么是注解 我们都知道在Java代码中使用注释是为了提升代码的可读性,也就是说,注释是给人看的 ...

  9. python中try...except的用法_提高开发效率,从避免滥用 try...except... 开始

    有不少人在写 Python 代码时,喜欢用 try...except... 一把梭,更有甚者一层套一层,不管有没有用,先套了再说: def func(): try: "函数内部代码" ...

最新文章

  1. 液体测量技术:从水到血液
  2. 坐视——做事——做势
  3. linux 子进程exit6,Linux内核之do_exit
  4. Yarn 内存分配管理机制及相关参数配置
  5. 基于ARM A53开发板,使用按键中断及中断底半部实现《led灯状态取反》的驱动
  6. linux/unix核心设计思想
  7. 奇瑞a3中控按键图解_实拍奇瑞全新瑞虎e 十万元级纯电SUV新选择
  8. A股收盘:深证区块链50指数跌1.75%,*ST群兴、亚联发展涨停
  9. SHELL(bash)脚本编程四:其他扩展
  10. LINUX Cacti 安装SOP FOR CentOS6.5
  11. 简单细胞自动机代码实现
  12. 手游最佳搭档:高续航音质卓越,高颜值精品蓝牙耳机推荐
  13. 包子笔记 - 关于沉没成本
  14. Kotlin学习笔记五、控制语句
  15. ちょっとした難しい言葉まとめ③
  16. php 字母数字下划线,CTF踩坑PHP编写一个不包含数字字母和下划线的后门
  17. anemometer mysql_十分钟部署Anemometer作为Mysql慢查询可视化系统
  18. android颜色透明度百分比
  19. 使用Kaptcha生成图片验证码
  20. 再度盈利后提“冷静增长”,爱奇艺守住长视频初心

热门文章

  1. R语言as.name函数(转化为命名的类别对象)和is.name函数(检验是否是命名的类别对象)实战
  2. python威氏符号秩次检验(Wilcoxon Signed-Rank Test)
  3. 使用pickle保存机器学习模型详解及实战(pickle、joblib)
  4. php中的css样式改变无反应,解决ecshop清除缓存css样式没反应问题
  5. The advantages of SMRT sequencing
  6. Type Ⅰ error(false positive) Type Ⅱ error(false negative)
  7. c++ char4个字节_西门子PLC的TCP通讯(不同项目下)①--TSEND_C指令
  8. 排班系统c语言设计说明,帮我设计一个关于员工排班的C语言程序
  9. setTimeOut()和setInterval()的用法
  10. python txt文件读写(追加、覆盖)