前端需要了解的HTTP缓存知识

6 篇文章 0 订阅
6 篇文章 0 订阅

HTTP缓存是一种用于提高网站性能和减少带宽使用的技术。当用户访问一个网页时,浏览器会下载页面上的所有资源(如HTML、CSS、JavaScript等),这些资源会占用大量的带宽和时间。为了减少这些资源的加载时间,HTTP缓存机制被引入。

缓存分为强缓存协商缓存两种,强缓存不能缓存地址栏访问的文件,协商缓存可以缓存地址栏访问的文件。

1、强缓存

由服务器设置过期时间,在该时间到期之前,浏览器会直接从本地缓存中获取资源。

强缓存的实现方式有两种:ExpiresCache-Control

1.1、Expires

Expires 是 HTTP 缓存机制中实现强缓存的一种方式,它通过在响应头部中加入一个过期时间来控制缓存。Expires 的值是一个日期,格式为:Wed, 21 Oct 2015 07:28:00 GMT。它表示该资源的过期时间。当浏览器再次请求该资源时,会判断是否在该过期时间内,如果是则直接从缓存中获取资源,否则重新向服务器请求。

要设置 Expires 头部,需要在服务器端进行配置。例如,在 Nginx 中可以使用expires指令来设置过期时间:

location / {
    expires 1h;
}

注意:由于Expires是基于客户端时间计算的,如果客户端的时间与服务器的时间不一致,则可能会影响缓存效果。

1.2、Cache-Control

Cache-Control 是通过在响应头部中加入 Cache-Control 字段,并设置max-age值来表示该资源在多少秒内有效(即缓存的最大时长)。

Cache-Control响应头的最常用格式为:

Cache-Control: max-age=<seconds> // seconds 是缓存的时间,单位是秒

当浏览器请求资源得到的响应头中带有 Cache-Control 响应头时,浏览器会将该资源缓存到本地。在下一次访问该资源时,同时满足下述条件,浏览器就会使用本地资源(即缓存),而不重新去服务器请求该资源:

  1. 缓存时间未过期

  2. URL未发生变化

  3. 请求头中没有 Cache-Control: no-cache 或 Pragma: no-cache 这两个信息(强制刷新会在请求头中添加 Cache-Control: no-cache

注意:直接通过浏览器的地址栏访问的资源缓存会失效(跟强制刷新一样会在请求头中添加 Cache-Control: no-cache

接下来我们通过一个简单的页面来实践一下:

目录结构,我们准备两个文件 index.js 和 index.html ,再准备两张图片:

http
|--- index.js
|--- index.html
|--- 1.jpg
|___ 2.jpg

index.js

提供一个 node 服务,用于返回浏览器请求的资源(index.html 和图片)

// index.js
const http = require('http')
const fs = require('fs')
const path = require('path')

const server = http.createServer((req, res) => {

  let filePath = path.resolve(__dirname, req.url === '/' ? `index.html` : '1.jpg')

  res.writeHead(200, {
    'Content-Type': req.url === '/' ? 'text/html; charset=utf-8' : 'image/png',
    'Cache-Control': 'max-age=86400', // 缓存一天
  })
  const fileStream = fs.createReadStream(filePath)
  return fileStream.pipe(res)
})

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})

server.listen(8080, () => {
  console.log(`opened server on http://localhost:${server.address().port}`)
})

index.html

一个简单的页面,包含一张图片,因为我们会直接通过浏览器的地址栏访问 html,所以 html 的缓存策略会失效。这里我们判断缓存是否生效是通过页面中的图片去判断的。

<!-- index.html -->
<!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>Hello World</title>
</head>
<body>
  <h1>Hello World!</h1>
  <!-- 别忘记替换成你的图片名称 -->
  <img src="./1.jpg" title="123">
</body>
</html>

 然后随便准备两张图片就可以了,我们在项目的根目录使用 node index.js 来启动项目。如下图提示,就表示启动成功,然后我们通过浏览器访问 http://localhost:8080/ 就能看到我们的页面了。

 首次请求 我们主要看图片的请求头跟响应头就行(因为html的缓存会失效)。

刷新页面(非强制刷新)

第二次的请求可以看到请求的 Size 变成了 memory cache,并且 Time 也变为了 0。再进一步看请求头和响应头,请求头中的 Cache-Control: no-cache 属性没有了,并且浏览器会给出一个警告:Provisional headers are shown. Disable cache to see full headers.。大致意思就是当前使用的是缓存的临时文件,禁用缓存后才可以查看完整的请求头。

验证缓存

上述的方法可能并不一定让你相信我们使用的是缓存文件,而不是重新请求的资源文件。

一开始我们准备了两张图片,现在使用的是 1.jpg,还有一个 2.jpg,我们把 1.jpg 删除了,然后把2.jpg 改名成 1.jpg,然后刷新页面(非强制刷新),就会发现虽然我们图片更改了,但是图片并不是我们后面改名的那个图片,还是之前的图片。

 

强制刷新后就能看到,我们替换的图片生效了,请求头中也带上了 Cache-Control: no-cache 属性。

2、协商缓存

利用浏览器和服务器之间的通信来确定是否需要重新获取资源。

协商缓存有两种实现方式:If-Modified-Since 和 ETag

当浏览器第一次请求资源时,服务器会返回该资源的 ETag 值和 Last-Modified 时间。当浏览器再次请求该资源时,它会将这些值作为请求头部的 If-Modified-Since 和 If-None-Match 字段发送给服务器。服务器会比较这些值与资源的当前状态,如果资源没有发生变化,服务器返回 304 Not Modified 响应,告诉浏览器可以使用缓存的资源。

如果资源已经发生了变化,服务器会返回新的资源,并更新 ETag 和 Last-Modified 。

2.1、If-Modified-Since 

利用响应头的 Last-Modified 来设置缓存,并在下次请求的请求头中携带 If-Modified-Since 来判断该资源是否发生变化,如果发生变化则返回新的资源,并更新 Last-Modified 属性,如果没有发生变化,则返回 304 跟空的 body

对强缓存的例子稍微修改一下

// index.js
const http = require('http')
const fs = require('fs')
const path = require('path')

const server = http.createServer((req, res) => {
  let filePath = path.resolve(__dirname, req.url === '/' ? `index.html` : '1.jpg')
  const stat = fs.statSync(filePath)
  const lastModified = stat.mtime.toUTCString()
  const header = {
    'Content-Type': req.url === '/' ? 'text/html; charset=utf-8' : 'image/png',
    'Last-Modified': lastModified
  }
  // 判断资源是否发生变化
  if (req.headers['if-modified-since'] === lastModified) {
    res.writeHead(304, header)
    res.end()
  } else {
    res.writeHead(200, header)
    const fileStream = fs.createReadStream(filePath)
    return fileStream.pipe(res)
  }
})

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})

server.listen(8080, () => {
  console.log(`opened server on http://localhost:${server.address().port}`)
})

首次请求

响应头携带 Last-Modified: Mon, 05 Jun 2023 08:57:17 GMT 属性,告诉浏览器这个文件需要缓存。

刷新页面(非强制刷新)

第二次请求,响应状态码变为 304,并在请求头中携带 If-Modified-Since: Mon, 05 Jun 2023 08:57:17 GMT 属性,表示浏览器使用缓存文件。

改变html文件

把 html 中 的 Hello World! 改为 Web Html,并刷新页面(非强制刷新),发现响应状态码变为 200 ,并且更新了页面和响应头的 Last-Modified 属性。

注意:如果资源的修改时间只精确到秒,而不是毫秒,可能会导致缓存失效。此外,如果服务器上的资源被修改了,但修改时间没有更新,也会导致缓存失效 

2、ETag 

ETag 基本上与 If-Modified-Since 一致, 利用响应头的 Etag 来设置缓存,并在下次请求的请求头中携带 if-none-match 来判断该资源是否发生变化,如果发生变化则返回新的资源,并更新 Etag 属性,如果没有发生变化,则返回 304 跟空的 body

const http = require('http')
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')

const server = http.createServer((req, res) => {
  let filePath = path.resolve(__dirname, req.url === '/' ? `index.html` : '1.jpg')
  const fileContent = fs.readFileSync(filePath)
  const hash = crypto.createHash('md5').update(fileContent).digest('hex')
  const etag = `"${hash}"` // etag需要加双引号
  const header = {
    'Content-Type': req.url === '/' ? 'text/html; charset=utf-8' : 'image/png',
    'Etag': etag
  }
  if (req.headers['if-none-match'] === etag) {
    res.writeHead(304, header)
    res.end()
  } else {
    res.writeHead(200, header)
    const fileStream = fs.createReadStream(filePath)
    return fileStream.pipe(res)
  }
})

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})

server.listen(8080, () => {
  console.log(`opened server on http://localhost:${server.address().port}`)
})

这里的测试证明就不写,如果有兴趣可以自己尝试一下

3.   工作原理

强缓存主要依赖于HTTP响应头中的两个字段:Cache-ControlExpires

  • Cache-Control:该字段包含多个指令,如max-agepublicprivate等,用于控制响应的缓存行为。
  • Expires:该字段设置一个过期时间,告诉浏览器在这个时间点之前可以直接使用缓存。

实现方法

  1. 设置Cache-Control字段,例如:Cache-Control: max-age=31536000,表示资源在一年内有效。
  2. 设置Expires字段,例如:Expires: Wed, 21 May 2025 07:28:00 GMT,表示资源在指定时间之前有效。

优点

  • 减少服务器请求,节省带宽。
  • 加快页面加载速度,提升用户体验。

缺点

  • 缓存数据可能过时,需要手动更新缓存。

结合使用

在实际开发中,通常会将强缓存和协商缓存结合使用,以实现最佳的缓存策略。首先通过强缓存减少不必要的请求,当强缓存失效时,再通过协商缓存确保数据的一致性。

创建一个名为server.js的文件,并添加以下代码来设置Express服务器。

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;

// 设置静态文件目录
app.use(express.static(path.join(__dirname, 'public')));

// 处理图片请求
app.get('/image.jpg', (req, res) => {
  const filePath = path.join(__dirname, 'public', 'image.jpg');
  const stats = fs.statSync(filePath);
  const etag = `"${stats.ino}-${stats.size}-${stats.mtime.getTime()}"`;

  // 强缓存设置
  res.set('Cache-Control', 'max-age=31536000, public');
  res.set('Expires', new Date(Date.now() + 31536000000).toUTCString());

  // 协商缓存设置
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }
  res.set('ETag', etag);
  res.set('Last-Modified', stats.mtime.toUTCString());

  // 发送图片
  res.sendFile(filePath);
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

在项目文件夹中创建一个名为public的子文件夹,并在其中放置你的图片文件image.jpg

node server.js 

打开浏览器并访问http://localhost:3000/image.jpg。浏览器将请求图片并根据设置的HTTP头部信息进行缓存。

  • 首次访问:服务器将发送图片和相关的HTTP头部信息,浏览器将图片存储在本地缓存中。
  • 再次访问:浏览器将检查本地缓存,并根据Cache-ControlExpires头部信息决定是否需要再次请求图片。如果图片在强缓存有效期内,浏览器将直接使用缓存的图片。
  • 过期后访问:当强缓存过期后,浏览器将发送带有If-None-MatchIf-Modified-Since的请求头。服务器将根据这些信息决定是否发送新的图片或返回304状态码。

这个简单的示例展示了如何结合使用HTTP强缓存和协商缓存来优化资源的加载和更新。在实际开发中,你可能需要根据项目的具体需求调整缓存策略。

结论

HTTP缓存是前端性能优化的重要手段之一。通过合理设置强缓存和协商缓存,可以显著提高页面加载速度,提升用户体验。开发者需要根据实际需求,选择合适的缓存策略,以达到最佳的性能效果。

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值