概述

http中的缓存控制是“浏览器缓存”,浏览器缓存就是将js、css、img这些前端资源缓存到浏览器。

浏览器缓存除了http设置,还有一种是在HTML页面中加入<meta http-equiv="Pragma" content="no-chache">,这个就是告诉浏览器不缓存当前页面,每次访问都要重新从服务器获取。但是只有部分浏览器支持,另外所有缓存代理服务器都不支持,因为代理不解析HTML内容本身。

浏览器第一次请求服务器的文件index.html,服务器回送200 OK的响应,响应体中返回文件index.html的内容,同时服务器会在回送的响应头中设置一个缓存有效期(http 1.0中是设置Expires头,http 1.1中都是设置Cache-Control:max-age=xxx),以及Last-Modified文件在服务器上的最后修改时间,以及文件的一个标记Etag,一共回送了三个响应头。浏览器会将文件内容以及该文件的响应头信息都保存到本地。需要注意的是,现代浏览器中,如果要禁止页面缓存,只需要设置Cache-Control响应头即可,但必须是Cache-Control:no-store才可以禁止页面缓存,no-cache无法禁止缓存。

浏览器第二次再请求这个index.html(相同的URL),由于本地已经有了这个文件的缓存,因此浏览器会检查缓存,如果缓存没有过期(检查Expires或Cache-Control:max-age,资源第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较),则不向浏览器发送请求,直接取缓存,此时会看到一个200 OK(from cache)的响应,此时浏览器与服务器是没有任何交互的。

而如果检查到缓存已经过期,则向服务器发送请求,这个请求头中会携带If-Modified-Since与If-None-Match两个头,这两个头信息的值是的缓存文件保存的上一次的响应中Last-Modified与Etag的值。服务器拿到了请求头中的If-Modified-None与Etag后会进行资源新鲜度检测,也就是服务器根据拿到的文件修改时间,判断服务器上的文件最后修改时间,判断文件是否修改过了,以及根据Etag,判断文件内容有没有发生变化。

若这两种判断的结论都是文件没有被修改过,那么服务器还是会回送一个304 Not Modified响应,但是不会在响应体回送文件内容,这个响应头告诉浏览器缓存没有变过直接用本地缓存。而如果这两种判断有任意一个未通过,说明文件在服务器上已经变过了,服务器就会受理这次请求,回送一个200 OK的响应,响应中携带所请求文件的内容。

注意两个概念,强缓存与协商缓存,强缓存就是本地缓存,也就是from memory cache与from disk cache。协商缓存也就是浏览器往服务器发送请求,请求服务器检查缓存是否过期,过期则返回200 ok附带新内容,未过期则返回304 not modified。如果资源已缓存过了,使用强缓存会导致一个问题,就是服务器资源已发生了改变但浏览器不知道还在使用本地的强缓存,此时可以让资源的url发生变化(一般就是加hash值),浏览器发现资源url变化了则会重新往服务器请求资源。

cache-control有种几个关键词,public允许客户端和代理服务器都可以缓存该资源。private是只让客户端可以缓存该资源,代理服务器不允许缓存该资源。no-cache表示不使用本地缓存,使用协商缓存,也就是浏览器每次往服务器发送请求检查缓存是否过期。no-store表示不进行缓存,也就没有强缓存与协商缓存了,每次请求都有服务器返回资源内容。

这个缓存机制,要么让浏览器直接不发送请求,要么发送请求之后服务器不需要回送完整的请求(响应体中不必携带文件数据),前者是减少http请求数量,后者是减少请求的带宽。

即便响应头设置了Cache-Control指定了max-age缓存有效期,在这个有效期内发送请求,响应状态有可能是200 From Cache,也有可能是304 Not Modified,而到底是哪个这要取决于用户在浏览器是以何种行为让浏览器发送请求的。

200 From Cache304 Not Modified
以下三种情况不往服务器发送请求,回送的响应是200 From Cache
1.在浏览器地址栏输入地址后按回车M
2. 通过某个链接跳转进入当前页面
3. 从当前页面跳转到了其他地方后,再按浏览器前进/后退按钮重新进入了当前页面

需要注意的,具体的显示有两种,from memory cache、from disk cache,前者是从内存中取缓存,后者是从磁盘中取缓存。请求获取到内容后,内容会被缓存到本地的磁盘以及浏览器的内存中。一旦浏览器关闭了,内存就释放掉了,那么内存缓存就不存在了,此时就只有从磁盘中取缓存,磁盘缓存会再加载到浏览器内存中,下一次再优先从内存中取缓存。
只有以下一种情况往服务器发送请求,服务器会回送304 Not Modified
1.通过刷新按钮刷新当前页面

这个例子中要注意的就是,如果Etag和Last-Modified一个都没有,那么服务器就无法进行文件变动检测也就无法返回304 Not Modified,Etag和Last-Modified至少有一个,或者两个同时都有,那么服务器就可以对要请求的文件在服务器上检查它是否变动过,而决定是否返回304 Not Modified还是200 OK。

与缓存相关的响应头

浏览器的缓存机制分为两块,Freshness和Validation。Fressness也就是所谓缓存新鲜度检测,就是是否直接取用本地缓存而不向浏览器发请求。Validation就是向服务器发送请求,服务器检测文件是否变动过而返回200还是304。

与Freshness相关的是Expires和Cache-Control响应头,为何会有这两个响应头这是由于历史原因,在http1.0中定义的是Expires,Expires的值是一个明确的过期时间(某年某月某日),而后来发现一旦客户端的时间与服务端时间不一致就会引起问题,因此在http1.1中新增了Cache-Control,实现更优更精细的缓存控制,比如max-age配置是一个秒数的有效期,告知浏览器多长时间不会过期,而不是告知一个过期日期。

与Validation相关的是Last-Modified和Etag,而之所以出现两个也是因为历史为难题,在http1.0中定义的是Last-Modified,它表示的是文件最后一次修改的时间,这个约定带来的问题是,一旦内容是动态生成的,这个时间在服务器端不一定可以正确生成,也就是内容动态生成变动了,但是文件的时间却没有变动,其次是Last-Modified只有秒级的精度,如果就正好在一秒内文件修改了,这样也会导致问题。因此在http1.1中引入了Etag,它的实现不尽相同,对于动态内容常规的做法是对动态内容做Hash计算作为Etag返回,对于静态资源一般是使用inode+mtime进行计算。

ETag也有他自己的问题,所以经常在使用中会关闭ETag。举例来说,同一个文件在不同物理机上的inode是不同的,这就导致了在分布式的Web系统中,当访问落在不同的物理机上时会返回不同的ETag,进而导致304失效,降级为200请求。解决办法也有从ETag算法中剥离inode,只是使用mtime,但是这样实际上和Last-Modified就一样了。当然你也可以额外的做一些改进,使ETag对静态资源的算法也是通过hash计算的。不过由于一般会使用CDN技术,因此集群部署中的Etag问题并不会造成太大的影响。


hebe700
729 声望15 粉丝

d