Expires, Last-Modified, Etag缓存机制

前言

最近遇到了一个问题,咱们团队开发了一个js库,提供给各个业务方引入,采集相关业务数据,渐渐发现,每次更新js库的代码,从新发布,并清除dns缓存后,仍是有些业务方在请求旧的js库的代码,怎么才能让业务方拿到最新的sdk代码呢?我陷入了困扰,每次从新发布修改js库的文件名?这彷佛不太合理,接入的业务方那么多,每次发布一个个去通知他们更改引入的文件名?怕是会被业务方们扔鸡蛋,且更改了文件名业务方也不会由于这个小改动从新发版,这显然不靠谱.因而我研究了一下浏览器的静态资源缓存的策略,原觉得nginx配置那里修改相应头的Etag字段,告诉浏览器我这个资源更新了,浏览器便会从服务器下载新的资源文件了.但后来通过一番折腾后,发现nginx只能保证ETag开启和关闭,也就是详情头里是否包含该字段,可是并不能保证资源更新后该ETag字段的更新,个人问题并无解决,可是花功夫仔细研究了一下Expires, Last-Modified, Etag的缓存机制.在此,我把我对浏览器的静态资源缓存策略的相关内容作一个分享,但愿能够帮助遇到相似困扰的童鞋们~vue

Cache-Control(缓存控制)

在开始重点内容的分享以前,我想先分享一下关于请求头和响应头中的Cache-Control字段,这个字段在http请求中充当着缓存控制的角色,是控制缓存的开关,用于标识请求或访问中是否开启了缓存,使用了哪一种缓存方式.nginx

Cache-Control通用消息头字段被用于在http请求和响应中经过指令来实现缓存机制,缓存指令是单向的,这意味着在请求设置的指令,在响应中不必定包含相同的指令.Cache-Control响应指令容许源服务器覆盖一个响应默认的缓存功能.web

Cache-Control字段浏览器

在请求中使用Cache-Control,它可选的值有:缓存

字段名称 说明
no-cache 告知(代理)服务器不直接使用缓存,要求向原服务器发起请求(这并不表明你每次均可以取到最新的资源)
no-store 全部内容都不会被保存到缓存或Internet临时文件中
max-age=delta-seconds 告知服务器但愿接收一个存在时间(Age)不大于delta-seconds秒的资源
min-fresh=delta-seconds 告知(代理)服务器客户端但愿接一个在小于delta-seconds秒内被更新过的资源
no-transform 告知(代理)服务器客户端但愿获取实体数据没有被转换(好比压缩)过的资源
only-if-cached 告知(代理)服务器客户端但愿获取缓存的内容,而不用向原服务器发去请求
cache-extension 自定义扩展值,若服务器不识别该值将被忽略掉

在响应中使用Cache-Control时,它可选的值有:服务器

字段名称 说明
public 代表任何状况下都得缓存该资源(即便是须要HTTP认证的资源)
Private[="field-name"] 代表返回报文中所有或部分(若指定了field-name则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user,如代理服务器)作缓存使用,其余用户则不能缓存这些数据
no-cache 不直接使用缓存,要求向服务器发起(新鲜度校验)请求(这就到了Etag和Last-Modified起做用的时候了)
no-store 全部内容都不会被保存到缓存或Internet临时文件中
no-transform 告知客户端缓存文件时不得对实体数据作出任何改变
only-if-cached 告知(代理)服务器客户端但愿获取缓存的内容(如有),而不用向原服务器发去请求
must-revalidate 当前资源必定是向原服务器发去验证请求的,若请求失败会返回504(而非代理服务器上的缓存)
proxy-revalidate 与must-revalidate相似,但技能应用于共享缓存(如代理)
max-age=delta-seconds 告诉客户端,该资源在delta-seconds秒内是新鲜的,无需向服务器发请求
s-maxage=delta-seconds 同max-age,但仅应用于共享缓存(如代理)
cache-extension 自定义扩展值,若客户端不识别该值将被忽略掉

须要注意的是,在Cache-Control中,这些值能够自由组合,多个值冲突时,是有优先级的,no-store优先级最高.post

缓存校验: 在缓存中,咱们须要一个机制来验证缓存是否有效.好比服务器的资源更新了,客户端须要及时刷新缓存,又或者客户端的资源过了有效期,可是服务器上的资源仍是旧的,此时并不须要从新请求资源.缓存校验就是用来解决这些问题的.url

Expires(缓存校验)

服务器设置Expires字段为一个日期,客户端请求该资源时将这个日期与客户端当前日期进行比对,若是当前时间小于这个日期,则表示资源未过时,使用缓存,若是当前时间大于这个日期,则表示资源已过时,客户端就会从新请求该资源.可是这一策略会收到客户端与服务器时间不一致的问题的影响.若是客户端时间晚于服务器的时间,会致使资源还未过时就从新请求,反之,会致使客户端还在使用过时的旧资源.代理

Last-Modified / If-Modified-Since(缓存校验)

当浏览器第一次请求一个资源时,服务端返回状态码200,返回请求的资源的同时HTTP响应头会有一个Last-Modified标记着文件在服务端最后被修改的时间.orm

浏览器第二次请求上次请求过的资源时,浏览器会在HTTP请求头中添加一个If-Modified-Since的标记,用来询问服务器该时间以后文件是否被修改过

若是服务器端的资源没有变化,则自动返回304状态,使用浏览器缓存,从而保证了浏览器不会重复从服务器端获取资源,也保证了服务器有变化时,客户端可以及时获得最新的资源.

Etag / If-None-Match(缓存校验)

当浏览器第一次请求一个资源时,服务端返回的状态码为200,同时HTTP相应头会有一个Etag字段,存放着服务器端生成的一个序列值.

浏览器第二次请求上次请求过的url时,浏览器会在HTTP请求头添加一个If-None-Match的标记,用来询问服务器该文件有没有被修改。

若是服务器的资源没有变化,Etag字段没有被修改依然与If-None-Match的值保持一致,则请求自动返回304状态,使用浏览器缓存.若是不一致,则说明资源被更改,则从新去下载新的资源.

Etag主要为了解决Last-Modified没法解决的一些问题:

(1) 一些文件也许周期性的更改,可是它的内容并不改变(仅仅改变的是修改时间),这个时候咱们不但愿客户端认为这个文件被修改了,而从新获取资源.

(2) 某些文件修改很是频繁,好比在秒一下的时间内进行修改(好比1s内修改了N次),If-Modified-Since能检查到的粒度是秒级的,这种修改是没法判断的(或者说UNIX记录MTIME只能精确到秒);

(3) 某些服务器不能精确的获得文件的最后修改时间;

nginx配置里ETag选项默认开启的,因此请求的资源文件若发生改动,会在响应头里生成新的ETag值.这样客户端就可以发现If-None-Match的值和Etag字段的值不匹配,从而去请求最新的资源文件.

个人困惑

静态资源修改之后.对应的文件最后更改时间和ETag字段有时彷佛都不会作相应的更改,浏览器缓存该静态资源文件,致使在文件更改后不能及时更新.是否是静态资源的缓存只能依靠修改文件名的策略来拿到最新的资源? 那么Expires, Last-Modified, Etag这三个字段只能在后台代码里才能修改吗?各路英雄可还有更好的解决方案?望各路英雄不吝赐教~

往期文章

在vue中使用SockJS实现webSocket通讯

postMessage可太有用了

手把手教你制做表格表头悬浮(table-header-fixed)

相关文章
相关标签/搜索