关于304缓存与200from cache缓存

2016-12-21

我们常见的浏览器缓存有两种,一种状态码是200,size显示是from-cache,另一种状态码是304,也就是通常我们说的304缓存

根据HTTP协议规范的定义,浏览器的缓存机制分为两块,也就是规范中的4.2. Freshness 和 4.3. Validation。

  • Freshness(新鲜度),这个条件成立,就是200 from cache缓存,
  • Validation(验证),当Freshness不成立时,就要看Validation,成立就是304缓存,不成立,拉取的就是新数据

但是实际情况,某些浏览器并不是完全按照协议来实现缓存机制的,这个后面讲,先看Freshness和Validation是什么东西

Freshness(新鲜度)

决定一个资源是不是足够新鲜,需要看资源的两个响应头Expires和Cache Control,他们是用来进行Freshness验证,也就是提供客户端检测文件是否足够新鲜,可以无需向服务端发起Validation请求就能保证并未过期可以直接使用。

Expires

http1.0版本定义的,明确的过期时间

1
expires:Sat, 30 Dec 2017 00:52:32 GMT

Cache Control

为什么要有Cache Control是由于历史原因,在HTTP1.0中定义的是Expires,Expires的值是一个明确的过期时间,是服务端时间,而后来使用中发现一旦客户端时间与服务器时间不一致就会引发很多缓存问题。所以在HTTP1.1中增加了Cache Control,其中的max-age是一个数值,单位(秒),告知客户端这个文件多长时间不会过期而不是直接告知过期时间。

如下,告诉客户端这个资源有效期一年

1
cache-control:public, max-age=31536000

详细看下Cache Control的值有哪些,因为Cache Control是通用Header,所以当时做为请求头和响应头时,其值的作用是不一样的

Cache-Control作为请求头的时候,是浏览器期望服务器返回什么的资源,有以下这些值,no-cache,no-store,max-age,max-state,min-fresh,no-transform,only-if-cached

Cache-Control作为响应头的时候,可以有以下这些值:public,private,no-cache,no-store,no-transform,must-revalidate,proxy-revalidate,max-age,s-maxage,cache-extension

这里很多值是给缓存服务器用的,对于浏览器端,主要说三种情况

no-store

这个值出现时,浏览器完全不缓存资源,所以不管新鲜度还是验证的都没啥事了,没有200 from cache,也没有304,每次请求都是重新向服务器拉新的资源

max-age大于0

首先缓存资源,然后资源的新鲜度是当前时间 + max-age指定的时间

no-cache 或 max-age=0

这两种可以理解为是一样的,即缓存资源,但其新鲜度立即过期,也就是一定需要发起Validation的

其他

比如没有配置Cache Control的情况(同时也没有配置Expires),其实这种情况是很多的,或者配置的是public,private之类的

这个时候资源的新鲜度有效期是是http连接建立时间减去文件最后修改时间的10%

可以这样理解

max-age = (Date - Last-Modified) * 10%



expires: 当前时间 + (Date - Last-Modified) * 10%

从上面的公式可以看出,如果一个资源刚被修改过,那么新鲜度时间就会很短,反之,如果长时间没被改动过,那么其新鲜度时间就会很长


所有的from cache的请求实际上都是由于浏览器认为本地的缓存资源足够新鲜,所以并没有发起http请求,而是直接从硬盘(from-disk-cache)或内存(from-memory-cache)中读取上次缓存的资源

缓存在硬盘重要不清空缓存,缓存有效期内,资源都是在的,和浏览器重不重启没有关系

如何更新这种缓存

所以资源被缓存后,更新版本时,就需要给资源加上时间戳,否则,是没法刷新缓存的,你不能指望的用户发现内容不对时还清空缓存试试

关于时间戳可以看这篇
非覆盖式静态资源发布

Validation(验证)

Last-Modified和ETag则是另一组控制信息,他们用来实现Validation。他们的职责是在本地缓存被浏览器判断可能不够新鲜的时候,会用这两组信息向服务器请求数据,如果服务器内容没有改变,那么约定服务器会返回304 HTTP Code表明这个缓存可以直接使用,无需重新拉取,而一旦服务器内容改变了就会返回200,同时返回新的文件内容。

所以Validation的意思是向服务端发送请求,Last-Modified或ETag验证资源是否有改动来判断是非使用缓存,所以304缓存是存在一个TTR时间(完成一次请求响应回路)

Last-Modified(http1.0)

表示的是文件最后修改的时间

1
Last-Modified:Wed, 28 Dec 2016 14:31:40 GMT

ETag(http1.1)

有了前面的Cache-Control铺垫,这里的ETag也很好理解了

Last-Modified的缺点:

  • 只有秒级精度,如果一秒内有多次修改,缓存会有问题,这算什么问题???
  • 如果文件不是正常更新的,比如一个老文件覆盖了新的文件,这…确实有问题,比如想滚回老版本,不过即使是老版本,发布前重新构建后也就不存在旧文件覆盖文件的问题了
  • 如果一个文件被修改,但是内容并没有变化,这时候我们喜欢不刷新缓存,但是Last-Modified只看文件修改日期,过期就刷新缓存,不管内容有没有变化

HTTP1.1中新引入了ETag,他的实现不尽相同,对于动态内容,常规做法是

  • 动态内容做HASH计算,作为ETAG返回
  • 静态资源,一般是使用inode+mtime进行计算。
1
ETag:"215d-53838ca8b3cc0"

But,在分布式系统中,相同文件在不同机器上的的inode是不一样的,这就导致了ETag不一样,进而导致304失效,所以实际使用上是会关闭ETag,还是依靠Last-Modified来做Validation判断

实际情况

实际上我在Chrome中打开控制台看到,很多情况,不管Expires和Cache Control有没有效,缓存都是200 from cache,
而当我打开Safari时,304出来了, 如下图,上面Safari,下面是Chrome

浏览器行为

对于加载一个页面,我们通常有三种方式

  • 输入url,回车打开页面
  • 链接打开
  • 刷新

对于静态资源缓存的处理,我测试的情况是,输入url和链接打开行为都是一致的,不一样的在于刷新

Safari和Firefox在刷新时,都会加在一个请求头 Cache-Control: max-age=0,也就是说,刷新的时候,即使资源是新鲜的,浏览器也会发起Validation,然后返回304

而Chrome在刷新时并不会带上Cache-Control: max-age=0,所以上图的情况就解释的通了

参考链接: