我们在web开发中常常会遇到这样的场景,有一些较大和常用的资源(例如图片、文档、js、css),在页面打开初始化的时候并不需要用到,而是在用户与页面互动操作触发了某些条件时才需要这些资源(例如我们打开微博可能并不是为了看热搜,但大多数时候我们会点进热搜查看热搜新闻)。
那么问题来了,如果用户去点击查看热搜时,我们才去加载热搜所需要的相应的资源数据,就显得很被动了。因为在最常用的功能中,来耗费时间等待,这对用户来说其实并不是一个非常好的体验。所以,我们首先想到的是提前将这部分资源加载到浏览器缓存中,本文主要介绍了http两个不同的缓存机制。
一、http1.0——使用Expires,Last-Modified
强制缓存:在http1.0时代,当客户端根据地址去获取图片、文档等缓存资源时,我们通过设置响应头中的Expires字段来给定一个时间点,并与放在响应体中的请求资源一同返回给客户端浏览器,并一起被保存于缓存中,下次我们再请求同一个路径地址拿资源时,浏览器强制请求从缓存中获取并返回200状态码。当浏览器的本地时间超过Expires字段设置的时间,缓存资源失效,再请求这个路径时需要去服务端重新获取资源。
协商缓存:对于一些可能变化较大的资源,我们需要他们及时作出更新,但是又想继续使用缓存这种高效的方式,此时我们便不再使用Expires,而是使用Last-Modified字段,用来记录一个资源的最后改动时间,同样将该字段连同资源一起发返回给客户端。浏览器第二次请求该路径时,顺带把该字段值放入请求头中,不过此时会换一个字段名,变成If-Modified-Since,若服务器的资源发生变化,那这个资源文件肯定会有一个新的变化修改时间,服务器将新时间与旧时间一对比,发现不一致,将新资源放到响应体,新时间放入响应头,返回给客户端,客户端对缓存资源作出更新,状态码为200。若新时间与旧时间一致,说明资源未变化,返回状态码304 Not Modified,浏览器收到后,便可从缓存中取资源。下面是详细的步骤流程:
1⃣️. 客户端根据具体路径获取资源,此时服务器端会将该资源的一些基本信息自动封装http对象(包含响应头,响应体),Expires 表示该资源什么时候过期,Last-Modified表示该资源最后修改时间。
2⃣️. 浏览器拿到资源时,放入缓存中,等待用户使用。
3⃣️. 当用户使用功能需要该资源时,先检查是强制缓存还是协商缓存,前者则直接取,若两者都有Last-Modified、Expires同时存在,以强制缓存优先。若只是协商缓存则再次向服务器发送相同路径的请求,此时的请求头中附带上该资源的最后修改时间。
4⃣️. 服务器根据路径,检查最新资源的基本信息,是否与请求的基本信息一致。若一致,返回结果304 NotModified,此时服务器不再返回资源,而是再冗余返回一份基本信息,表示客户端缓存资源可用。若不一致,则返回状态码200和最新资源,还有最新资源的基本信息。当请求失败,也就是后台服务器挂掉的时候,这里也是返回304,继续使用缓存旧资源。
http1.0返回304示例如下:
一、http1.1——使用Cache-Control,age,Etag
在上张图片中我们发现返回头中有Cache-Control,Etag这些字段,其实这些也是服务器端对浏览器作缓存控制用到的字段,只不过是后来升级换代新加入的。在使用http1.0的时候我们发现,当本地时间与服务器端的时间不一致,或者是部署集群时,地域跨度太大,很容易引起缓存失效的问题,而且同时有两份修改时间一致的文件互相进行替换的时候,我们很难及时在浏览器端作出响应。因此我们在想,能不能直接设置一个时间段,而不是时间点的方式来对资源有效去作校验,使用资源内容的哈希值、散列值去校验资源变化,这便是http1.1。在http1.1中,还是会分为强制缓存、协商缓存两种方式,只不过对时间、内容的校验方法做了优化。
强制缓存:1⃣️客户端请求地址路径第一次获取资源,此时服务器端在响应头中设置放入两个字段:age和Cache-Control。age用来表示资源在客户端缓存中存在了多久,因为是第一次获取到的资源,所以初始化为0。Cache-Control用来表示资源有效期,即它能在客户端缓存中存在多久,例如Cache-Control:max-age=3600 表示3600秒内,缓存中的资源都是有效的可以直接使用的。
2⃣️在3600秒内,客户端请求相同路径时,强制从缓存获取,并返回状态码200。
3⃣️一旦资源占据缓存的时间超过3600秒,客户端重新从服务器获取资源,返回状态码200。
协商缓存:1⃣️客户端第一次获取资源时,服务器在返回头放入字段Cache-Control和Etag。此时的Cache-Control与强制缓存的有点不同,不再是max-age和时间段,而是一个字符串固定值"no-cache"。Etag中放入的值一般为资源内容的哈希或者散列值。与返回体中的资源一同返回给客户端,并将其持久化至缓存。
2⃣️客户端需要再次用到这个地址路径下的资源时,先去缓存中查看该资源的Cache-Control的值,发现为"no-cache",这时客户端知道了,需要先去后台做资源是否发生变化的校验,于是将该资源的Etag值发给后台,这里Etag的字段名发生变化,变成If-None-Match。
3⃣️后台接收请求旧Etag后与最新的Etag进行比较,若一致,返回304,若不一致,返回200,并在响应体中放一份最新资源,响应头中放一份最新Etag。
4⃣️强制缓存与协商缓存并存时:即Cache-Control值为"max-age=3600,no-cache",以强制缓存优先。
注:在上图中我们发现1.0和1.1两种写法并存,其实是为了更好的兼容低版本浏览器,因为两个版本各自校验并不冲突。