需求:
前端每次发版完,有部分前端文件是更新了的,但是客户访问却拿不到最新的前端文件,总不能让每个客户都手动清除浏览器缓存。
要实现让浏览器自己去获取最新的前端文件。
原理:
Nginx给请求的响应头中添加不缓存的策略。
设置缓存策略为不缓存:Http 1.1
add_header Cache-Control no-cache;
设置缓存策略为不缓存:Http 1.0
add_header Pragma no-cache;
设置缓存启用和缓存时间:Http 1.0
add_header Expires 0; # 缓存时间为0,即立即过期
为了向下兼容,可以将Http 1.0的配置也写上:
add_header Pragma no-cache;
add_header Expires 0;
优先级从高到低:
Pragma -> Cache-Control -> Expires
同时出现Pragma和Cache-Control时,以Pragma为准。
同时出现Cache-Control和Expires时,以Cache-Control为准。
按文件类型进行缓存:
而前端文件常见的有:html、js、css等。
针对不同的文件类型,可以有不同的缓存策略。
如果你的前端每次发版js和css文件名都会携带hash码,即每次发版后的文件名都不一样,客户端自然能拿到最新。
那么只需要针对html文件设置不缓存的请求头策略。
写法一:
前端静态文件
location ~* \.(gif|jpg|jpeg|png|css|js|ico|eot|otf|fon|font|ttf|ttc|woff|woff2)$ {
root /var/www/zhian_cloud/;
}
前端html文件
location / {
对html文件禁用缓存,任何时候都不缓存,拿到最新的
add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0';
root /var/www/zhian_cloud/;
index index.html index.htm;
try_files $uri /index.html;
}
写法二:直接匹配html文件,单独添加响应头
location / {
index index.html;
root /var/www/zhian_cloud/;
if (request_filename \~\* .\*\\.(?:htm\|html)) {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
try_files $$uri /index.html;
}
Cache-Control
Cache-Control也是一个通用首部字段,这意味着它能分别在请求头和响应头中使用。
作为请求头时,可选值有:
no-cache:
告知(代理)服务器不直接使用缓存,要求向源站服务器发起请求。
no-store:
所有内容都不会被保存到缓存或Internet临时文件中。
max-age=delta-seconds:
告知服务器客户端希望接收一个存在时间(age)不大于delta-seconds秒的资源。
max-stale[=delta-seconds]:
告知(代理)服务器客户端愿意接收一个超过缓存时间的资源,若有定义delta-seconds则为delta-srconds秒,若没有则为任意超出的时间。
min-freash=delta-seconds:
告知(代理)服务器客户端希望接收一个在小于delta-seconds秒内被更新过的资源。
no-transform:
告知(代理)服务器客户端希望获取实体数据没有被转换(比如压缩)过的资源。
only-if-cached:
告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向源站服务器发去请求。
cache-extension:
自定义扩展值,若服务器不识别该值将被忽略。
作为响应头时,可选值有:
public:
表名任何情况下都得缓存该资源(即使是需要http认证的资源)。
Private[="field-name"]:
表明返回报文中全部或部分(若指定了field-name则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user,如代理服务器)做缓存使用,其他用户则不能缓存这些数据。
no-cache:
不直接使用缓存,要求向服务器发起(新鲜度校验)请求。
no-store:
所有内容都不会被保存到缓存或Internet临时文件中。
max-age=delta-seconds:
告知客户端该资源在delta-seconds秒内是新鲜的,无需向服务器发请求。
s-maxage=delta-seconds:
同max-age,但仅应用于共享缓存(如代理)。
no-transform:
告知客户端缓存文件时不得对实体数据做任何改变。
only-if-cached:
告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向源站服务器发去请求。
must-revalidate:
当前资源一定是向源站服务器发去验证请求的,若请求失败会返回504(而非代理服务器上的缓存)。
proxy-revalidate:
与must-revalidate类似,但仅能应用于共享缓存(如代理)。
cache-extension:
自定义扩展值,若服务器不识别该值将被忽略。
public和private的选择
如果你用了CDN,你需要关注下这个值。CDN厂商一般会要求cache-control的值为public,提升缓存命中率。
如果你的缓存命中率很低,而访问量很大的话,可以看下是不是设置了private,no-cache这类的值。
如果定义了max-age,可以不用再定义public,它们的意义是一样的。
缓存校验
在缓存中,我们需要一个机制来验证缓存是否有效。比如服务器的资源更新了,客户端需要及时刷新缓存;又或者客户端的资源过了有效期,但服务器上的资源还是旧的,此时并不需要重新发送。缓存校验就是用来解决这些问题的,在http 1.1 中,我们主要关注下Last-Modified 和 etag 这两个字段。
1、Last-Modified
服务端在返回资源时,会将该资源的最后更改时间通过Last-Modified字段返回给客户端。客户端下次请求时通过If-Modified-Since或者If-Unmodified-Since带上Last-Modified,服务端检查该时间是否与服务器的最后修改时间一致:如果一致,则返回304状态码,不返回资源;如果不一致则返回200和修改后的资源,并带上新的时间。
If-Modified-Since和If-Unmodified-Since的区别:
If-Modified-Since:告诉服务器如果时间一致,返回状态码304。
If-Unmodified-Since:告诉服务器如果时间不一致,返回状态码412。
2、etag
单纯的以修改时间来判断还是有缺陷,比如文件的最后修改时间变了,但内容没变。对于这样的情况,我们可以使用etag来处理。
etag的方式是这样:服务器通过某个算法对资源进行计算,取得一串值(类似于文件的md5值),之后将该值通过etag返回给客户端,客户端下次请求时通过If-None-Match或If-Match带上该值,服务器对该值进行对比校验:如果一致则不要返回资源。
If-None-Match和If-Match的区别:
If-None-Match:告诉服务器如果一致,返回状态码304,不一致则返回资源。
If-Match:告诉服务器如果不一致,返回状态码412。
Http状态码304:
当客户端(通常是浏览器)向web服务器发送一个请求,如果web服务器返回304响应,他不包含任何响应的内容,只是提示客户端缓存的内容是最新的,可以直接在客户端的缓存中获取。这种方法可以节省带宽,避免重复响应。
用户刷新访问行为
1、在URL输入栏中输入然后回车/通过书签访问
可以看到返回响应码是 200 OK (from cache),浏览器发现该资源已经缓存了而且没有过期(通过Expires头部或者Cache-Control头部),没有跟服务器确认,而是直接使用了浏览器缓存的内容。其中响应内容和之前的响应内容一模一样,例如其中的Date时间是上一次响应的时间。
2、F5/点击工具栏中的刷新按钮/右键菜单重新加载
F5的作用和直接在URI输入栏中输入然后回车是不一样的,F5会让浏览器无论如何都发一个HTTP Request给Server,即使先前的响应中有Expires头部。
其中Cache-Control是Chrome强制加上的,而If-Modified-Since是因为获取该资源的时候包含了Last-Modified头部,浏览器会使用If-Modified-Since头部信息重新发送该时间以确认资源是否需要重新发送。 实际上Server没有修改这个index.css文件,所以返回了一个304(Not Modified),这样的响应信息很小,所消耗的route-trip不多,网页很快就刷新了。
3、Ctl+F5
Ctrl+F5是彻底的从Server拿一份新的资源过来,所以不光要发送HTTP request给Server,而且这个请求里面连If-Modified-Since/If-None-Match都没有,这样Server不能返回304,而是把整个资源原原本本地返回一份,这样,Ctrl+F5引发的传输时间变长了,自然网页Refresh的也慢一些。我们可以看到该操作返回了200,并刷新了相关的缓存控制时间。
实际上,为了保证拿到的是从Server上最新的,Ctrl+F5不只是去掉了If-Modified-Since/If-None-Match,还需要添加一些HTTP Headers。
例如请求头中会添加以下两个字段:
Cache-Control: no-cache Pragma: no-cache
按照HTTP/1.1协议,Cache不光只是存在Browser终端,从Browser到Server之间的中间节点(比如Proxy)也可能扮演Cache的作用,为了防止获得的只是这些中间节点的Cache,需要告诉他们,别用自己的Cache敷衍我,往Upstream的节点要一个最新的copy吧。