HTTP缓存这么重要,快来了解下

963 阅读6分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

前言

提到缓存,想必各位都能想到它的作用 —— 是性能优化的一个方面,用来节约时间,提升效率,在前端有很多场景都有用到缓存

  • webpack V5cache配置选项(Cache the generated webpack modules and chunks to improve build speed,通过缓存生成的webpack modules,chunks来提升构建速度)
  • webpack 打包项目时,通过在一些性能开销较大的 loader 之前添加cache-loader来提升构建的速度。
  • babel-loader也有提供cacheDirectory的配置,目的也是为了提升构建速度。
  • 我们平时在写代码时,也可能会对一些不会经常变动的数据进行缓存,第一次加载后就存到一个变量,下次再加载先判断缓存中有没有,有就从缓存返回,没有才发送http请求获取。
  • 甚至在项目中对axios进行封装时,会对请求进行JS层面的缓存,简单来说就是根据请求接口,请求类型,请求参数生成一个随机字符串当作缓存的key,第一次请求成功后将 response 存到一个 store并设置有效期,之后的请求,只要缓存的key存在store中并且没有失效就直接返回store中的数据。合理设置有效期,设置缓存的大小效果还是很棒的,算是一种牺牲空间换时间的做法。
  • 当然HTTP也有自己的缓存策略,接下来主要聊聊HTTP缓存

HTTP缓存

概念

HTTP缓存存储请求的响应,并将存储的响应重用于后续请求。

能缓存的请求

常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。

缓存的类型

按照缓存可存放的位置可分为 私有的缓存共享的缓存

私有的缓存

私有缓存是绑定到特定客户端的缓存,通常是浏览器缓存,存储的响应只在自己客户端,可以存储该用户的个性化响应,通过在响应头设置Cache-Control: private指定。

共享的缓存

位于客户端和源服务器之间的缓存,比如代理服务器,反向代理服务器等

如何控制缓存

通过服务端在响应头添加一些标识来控制缓存:

Cache-control

Cache-control有以下几个选项:

  • no-store  不允许使用缓存
  • no-cache 可以缓存,但是不论缓存是否过期,要先与服务器进行协商才可以使用
  • private 表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中
  • public  表示该响应可以被任何中间人(比如中间代理、CDN 等)缓存
  • max-age=<seconds> 表示资源能够被缓存的最大时间
  • must-revalidate 必须先验证缓存是否过期,已过期的缓存要先与服务器进行协商

Etag

缓存的一种强校验器,ETag 响应头是一个对浏览器来说无需理解,只需要按规定使用即可的值。浏览器不知道 ETag 代表什么,不能预测它的值是多少。如果资源请求的响应头里含有 ETag,客户端可以在后续的请求的头中带上 If-None-Match 头来验证缓存

Last-Modified

使用显式时间指定资源上一次修改日期,作为一种弱校验器。说它弱是因为它只能精确到一秒。如果响应头里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存

Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT

Expires

使用显式时间来指定缓存的生存期

Expires: Tue, 28 Feb 2022 22:22:22 GMT

Pragma

Pragma 效果与Cache-Control: no-cache 相同,通常设置用来兼容HTTP1.0

验证缓存是否可用

按照缓存是否可用还可以将缓存分为强缓存协商缓存

强缓存

有效的(未过期的)缓存,可直接使用,返回200from memory cache / from disk cache

举个🌰,先打开掘金网页,再刷新,能看到这个资源就是from memory cache image.png 再看看下面几个,还有from disk cacheimage.png

注意到控制台面板有个Disable cache的选项,不要勾选才能看到效果哦,勾选代表禁用了缓存。

协商缓存

过期的缓存,需要带上相应的请求头跟服务器协商看能否使用,能否使用取决于请求资源是否有更改。

下面看下是怎么验证的。

Last-Modified + If-Modified-Since

通过express模拟一个服务器来测试下:(至于如何搭建一个express服务,在此不过多介绍,请参考Express - 基于 Node.js 平台的 web 应用开发框架 - Express 中文文档 | Express 中文网 (expressjs.com.cn)

// app.js
const express = require('express')

const app = express()
const options = {
  etag: false,
  setHeaders: function (res, path, stat) {
    res.set('Cache-Control', 'max-age=60') // 设置一分钟有效期
  }
}
app.use('/static', express.static('public', options))
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <p>test</p>
  </body>
  <script src="./javascripts//index.js" type="application/javascript"></script>
</html>
const a = 123456789
启动服务

image.png

首次访问

image.png

我们主要看index.js这个文件,Cache-Control: max-age=60代表着在未来的一分钟内,这个资源都是有效的, 也就是说在一分钟内所有这个资源的请求都不会向源服务器请求,而是直接从缓存读取

在有效期内刷新:

image.png

有效期之外刷新

当一分钟过后,缓存就过期了,再次请求这个资源,浏览器会带上If-Modified-Since:Sun, 31 Jul 2022 14:31:09 GMT请求头和源服务器协商资源是否可用。

此时刷新: image.png 返回304 Not Modified, 表示浏览器可以使用本地缓存文件。304 的响应头同时更新缓存文档的过期时间。(没有更改

更改服务器文件刷新

如果我们先把index.js的内容改了,再刷新会是怎么样呢?

更改index.js

image.png

刷新: 返回200 ok表示有更改

Etag + If-None-Match

开启Etag

app.js

const express = require('express')

const app = express()
const options = {
  etag: true,
  setHeaders: function (res, path, stat) {
    res.set('Cache-Control', 'max-age=60')
  }
}
app.use('/static', express.static('public', options))

访问: image.png

同样有效期一分钟。

在有效期内刷新
过期后刷新

缓存的好处

  1. 由于不需要将请求传递到源服务器,因此客户端和缓存越近,响应速度就越快。最典型的例子是浏览器本身为浏览器请求存储缓存。
  2. 当响应可重用时,源服务器不需要处理请求,这减少了服务器上的负载。

总结