浏览器 HTTP 缓存是一种常见的 web 性能优化的手段,也是在前端面试中经常被考察的一个知识点。本文通过配置 koa2 服务器,在实践中带你探究浏览器的 HTTP 缓存机制。
先来直观认识一下浏览器 HTTP 缓存:
上面是打开浏览器后直接访问 V2EX 首页后的截图,矩形圈起来的那块也就是 size 部分显示的都是 from disk cached
,说明这些资源命中了强缓存,强缓存的状态码都是 200。
再来看看我直接访问上面箭头指向的那张图片是什么情况:
缓存判断规则是怎么实现的
其实所有的网络协议都是一套规范,客户端和服务器端是怎么只是按照规范来实现而已。浏览器 HTTP 缓存也是如此,浏览器在开发的时候便按照 HTTP 缓存规范来开发,我们开发的 HTTP 服务器也应该遵守其规范。当然了,服务器是你自己写的,你完全可以不按规范来,但是浏览器不知道你在搞什么名堂啊,HTTP 缓存肯定不会正常工作了。
我们知道浏览器和服务器进行交互的时候会发送一些请求数据和响应数据,我们称之为HTTP报文。
浏览器的 HTTP 缓存协议本质上就通过请求响应过程中在首部中携带那些和缓存相关的字段来实现的。
浏览器 HTTP 缓存的分类
浏览器 HTTP 缓存分两钟:
- 强缓存
- 协商缓存
强缓存指的是浏览器在本地判定缓存有无过期,未过期直接从内存或磁盘读取缓存,整个过程不需要和服务器通信。
协商缓存需要向服务器发送一次协商请求,请求时带上和协商缓存相关的请求头,由服务器判断缓存是否过期,未过期就返回状态码 304,浏览器当发现响应的返回码是 304,也直接是读取本地缓存,如果服务器判定过期就直接返回请求资源和 last-modified,状态码为 200。
浏览器请求资源时判定缓存的简略流程如下图:
文字解释一下:当浏览器请求一个资源时,浏览器会先从内存中或者磁盘中查看是否有该资源的缓存。如果没有缓存,可能浏览器之前没访问过这个资源或者缓存被清除了那只能向服务器请求该资源。
如果有缓存,那么就先判断有没有命中强缓存。如果命中了强缓存则直接使用本地缓存。如果没有命中强缓存但是上次请求该资源时返回了和协商缓存相关的响应头如 last-modified 那么就带上和协商缓存相关的请求头发送请求给服务器,根据服务器返回的状态码来判定是否命中了协商缓存,命中了的话是用本地缓存,没有命中则使用请求返回的内容。
强缓存和协商缓存的区别
- 命中时状态码不同。强缓存返回 200,协商缓存返回 304。
- 优先级不同。先判定强缓存,强缓存判断失败再判定协商缓存。
- 强缓存的收益高于协商缓存,因为协商缓存相对于强缓存多了一次协商请求。
演示服务器说明
整个 koa2 演示服务器在这:koa2-browser-HTTP-cache。总共就几个文件,index.js 入口文件,index.html 首页源代码,sunset.jpg 和 style.css 是 index.html 用到的图片和样式。
服务器代码代码很简单,使用 koa-router 配了三个路由,目前还没有写缓存相关的代码。
// src/index.js
const Koa = require('koa');
const Router = require('koa-router');
const mime = require('mime');
const fs = require('fs-extra');
const Path = require('path');
const app = new Koa();
const router = new Router();
// 处理首页
router.get(/(^\/index(.html)?$)|(^\/$)/, async (ctx, next) => {
ctx.type = mime.getType('.html');
const content = await fs.readFile(Path.resolve(__dirname, './index.html'), 'UTF-8');
ctx.body = content;
await next();
});
// 处理图片
router.get(/\S*\.(jpe?g|png)$/, async (ctx, next) => {
const { path } = ctx;
ctx.type = mime.getType(path);
const imageBuffer = await fs.readFile(Path.resolve(__dirname, `.${path}`));
ctx.body = imageBuffer;
await next();
});