文章分为三部分,我们先来统一梳理一下一个缓存请求的过程,然后从请求头以及响应头缓存相关字段进行解析,最后总结一下前端需要了解的对于缓存的操作
当一个用户发起一个静态资源请求的时候,浏览器会通过以下几步来获取资源
当第一次发送请求,http返回200的状态码,
如果没有关闭缓存请求的话(没标明不使用缓存,下述)则会在返回头中返回包含last-Modified以及Etag和Expires的字段(这些字段下面慢慢说),然后将文件保存在Cache目录下;
当后续请求该文件时候,先在本地查找该资源,如果在本地缓存找到对应的资源,但是不知道该资源是否过期或者已经过期, 则发一个http请求到服务器,然后服务器判断这个请求,
如果请求的资源在服务器上没有改动过,则返回304, 让浏览器使用本地找到的那个资源
而如果当服务器发现请求的资源已经修改过,或者这是一个新的请求(本地无对应资源),服务器则返回该资源的数据,并且返回200,
当然这个是指找到资源的情况下,如果服务器上没有这个资源,则返回404
经过上面的流程梳理,我们基本了解整个缓存处理过程,不过对于前端来说,我们需求无非就是使用缓存或者不使用缓存,在了解下面内容之后我们再根据原理去慢慢实现我们的需求。
二 . http头部缓存相关key:
首先我们要先根据http请求头以及响应头 先看一些跟缓存相关的报文头cache-control, if-none-match, if-modified-since, Etag,expires, last-modified,
这些在上一篇文章HTTP前端详解中也提及过留在这篇文章进行分析;
request header缓存相关:
cache-control:
其缓存指令对于前段常用的有如下no-cache、no-store、max-age这几个值;分别来说一下
no-cache:
表面意为“数据内容不被缓存”,而实际数据是被缓存到本地的,只是每次请求时候直接绕过缓存这一环节直接向服务器请求最新资源,由于浏览器解释不一样,
例如ie中我们设置了no-cache之后,请求虽然不会直接使用缓存,但是还会用缓存数据与服务器数据进行一致性检测(也就是说还是有几率会用到缓存的),
firefox中则完全无视no-cache存在,详细解释见no-store;no-store:
指示缓存不存储此次请求的响应部分。与no-cache比较来说,一个是不用缓存,一个是不存储缓存;按理来说这个设置更加粗暴直接禁用缓存,
但是具体实现起来 浏览器之间差异却特别大,一般不会直接用该字段进行设置,不过no-store是为了防止缓存被恶意修改存储路径导致信息被泄露而设置的,
毕竟有它的用处,在firefox中实现缓存是通过文件另存为将缓存副本保存到本地,直接利用no-cache对其是无效的,如果加上no-store设置的话 则可以起到与no-cache一样的效果;
即:cache-control:no-cache,no-store;可以确保在支持http1.1版本中各大浏览器回车后退刷新无缓存;
再加上Pragma: no-cache设置兼容版本1.0即可(不过为了防止一致性检测时候的万一我们还是最好加上一致性检测的内容,如下所示几种方式);
max-age:
例如Cache-control: max-age=3;表示此次请求成功后3秒之内发送同样请求不会去服务器重新请求,而是使用本地缓存;同样我们如果设置max-age=0表示立即抛弃缓存直接发送请求到服务器
一致性检测分为两种方式:1.检测日期是否过期,检测资源是否更新;if-none-match:
该字段与响应中的eTag一起使用,表示检查实体是否有更新改变;客户端第一次发送请求时候响应报文会包含字段Etag,表示资源状态,当资源改变后该值也会改变(客户端不必关心该值怎么生成)
然后缓存保存下该字段,第二次已经有该缓存时候在浏览本地缓存时候会将该值赋给if-none-match字段发送给服务器,服务器将发送的值与当前的状态进行对比,
如果值一样的话则答复304去使用缓存数据,如果值改变了则发送最新数据给客户端替代现有缓存数据,并且返回状态200;
if-modified-since:
该字段与last-modified配合使用,跟上述原理差不多,都是响应端先返回一个last-modified时间字段,再次请求时候 request头部会将缓存中的last-modified字段拿出来赋给if-modified-since,
发送给服务器,服务器去判断时间是否过期,如未过期则返回304,告诉客户使用缓存数据,如果过期则重新返回一个last-modified并且返回200;
repsonse header缓存相关:
Etag:
刚才也说过 是跟if-none-match配合去使用,它根据实体内容生成的一段hash字符串(类似于MD5或者SHA1之后的结果),可以标识资源的状态。 当资源发送改变时,ETag也随之发生变化。
使用Etag主要是为了解决根据时间无法解决的问题:比如文件修改频繁(秒之内修改),导致根据时间无法判断是否更新;以及修改时间变了,但是内容没变(我们应该认为该文件是没变的)
expires:
表示缓存过期时间例如:expires:Mon Dec 30 2011 11:01:19 GMT,跟cache-control中的max-age作用一样,不过在碰见max-age之后,该值会被覆盖从而被max-age替代;
last-modified:
表示文件最后修改时间;
另外响应端的报文头也有对cache-content的规定,与request大体相同,另有未提及的 欢迎大家补充
三 . 实现有关前端对于缓存的操作:
使用缓存:
默认情况下,浏览器都会使用缓存数据,
在f5刷新情况下 浏览器会发送一致性验证去服务器验证是否使用缓存,而浏览器直接回车则表示直接应用缓存不需要去服务器验证;所以我们就按照f5刷新去解释实现使用缓存:
一般来说前端默认是使用缓存的,默认情况下服务器端以及前端都会使用缓存数据,或者是根据etag或者是根据max-age或者是根据expires根据服务器不同去不同实现;
实际中大部分不需要我们手动去实现,而有些我们不确定是否使用缓存的情况下我们可以手动加以干涉强制使用缓存数据:
例如某个静态文件包括html或者图片我们需要使用缓存来提高处理速度,
方法一:
在服务器进行配置其max-age或者expires使其设置一个过期值为当前一年之后。这样每次进行检验时候都会使用缓存中文件.例如在.htaccess中
<IfModule mod_headers.c>
<FilesMatch ".(gif|jpg|jpeg|png|ico)$">
Header set Cache-Control "max-age=604800"
</FilesMatch>
方法二:
前端设置if-modified-since去设置一个上次修改时间大于当前日期,
方法三:
服务器端根据etag去判断是否匹配来根据实际业务来使用缓存;
后面两个方法属于弱缓存数据头,需要浪费http连接,所以建议使用第一种方式;
禁用缓存:
方法一:
可以在meta标签标明<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
方法二:
也可以动态去setRequestHeader,强制不用缓存设置组合如下:
cache-control='no-cache,no-store'
pragma='no-cache'
if-modified-since=0;
方法三:
请求端设置if-modified-since为已经过期的某个时间,可以是几年前或者几十年前。
方法四:
服务端设置Expires为过期某个时间,例如PHP中header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
实际开发中如果需要一致性检测则尽量去配合Etag以及last-Modified去进行比较然后返回使用缓存还是新数据;这个有点偏服务器端了,不再赘述方法五:
url后面加随机数或者时间戳url += “&random=” + Math.random()这个方法js以及php经常用,原理就是每个请求的url都不一样这样一来缓存中找不到对应数据,就自动去服务器寻找最新资源;
最后回到上篇开始提到一个问题;请求服务器时间,由于缓存导致时间返回问题:
问题回顾:由于没有设置缓存,默认浏览器是读取缓存数据的,导致我没清缓存情况下一直使用缓存数据,这样一来我就取不到真正的当前服务器时间了。
解决方式:利用上述方法二+方法四,动态在请求时候修改header以及根据随机数去请求最新数据,问题解决!!!
总结:
关于缓存 这篇文章有点高开低走的感觉,实际问题是可以通过各种手段解决的,例如google或者咨询大牛,但是原理还是应该掌握的,
因为关于某一方面的问题不仅仅工作中只遇到这一次而且还是一样的问题,自己掌握了,才能更好的去处理解决问题。