之前看八股文的时候都是强行记,对缓存没有一个实战层面的认知,这一次参考了一些博客,打算好好的把缓存这一块整一整。
首先简单介绍下本次代码:
index.html
server.js 用于创建一个后端服务器
resource.js 是index.html中请求的资源文件,用于测试缓存功能。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" href="data:;base64,="><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>cache</title><script src="resource.js"></script></head><body><h1>cache</h1></body></html>
console.log('this is resource.js')
let http = require('http'),fs = require('fs'),path = require('path'),url = require("url")http.createServer((req,res)=>{let pathname = __dirname + url.parse(req.url).pathname; // 获取文件路径let statusCode = 200 // 响应头状态码let extname = path.extname(pathname) // 获取文件扩展名let headType = 'text/html' // 内容类型if( extname){switch(extname){case '.html':headType = 'text/html'break;case '.js':headType = 'text/javascript'break;}}fs.readFile(pathname, function (err, data) {res.writeHead(statusCode, {'Content-Type': headType,/***** 设置缓存响应头的地方 ******/});res.end(data);});}).listen(80)console.log("Server running at http://127.0.0.1:80/");
一、强缓存
1.Expires
这是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 。这个字段是服务器响应头中的字段,用于告诉客户端资源文件缓存到期时间。
语法:Expires: <http-date>
eg:Expires: Wed, 21 Oct 2015 07:28:00 GMT
具体在代码中如下所示:
// 省略其他代码fs.readFile(pathname, function (err, data) {res.writeHead(statusCode, {'Content-Type': headType,// 设置文件过期时间为10秒后'Expires':new Date(Date.now() + 10000).toUTCString()});res.end(data);});
当在10s内第二次访问[http://127.0.0.1/index.html](http://127.0.0.1/index.html)时,在network中会有memory cache的标识:
在resource.js的response header中如下:
Date是响应时间。
Expires缺点:
Expires 定义的缓存时间是相对于服务器上的时间而言的,而浏览器在判断的时候是基于客户端的系统时间的,如果用户修改了自己电脑的系统时间,那么这个缓存时间将没有任何意义。
2.Cache-Control
针对Expires的缺点,HTTP 1.1提供了一个新的强缓存标准,该标准并不设置缓存过期的绝对时间,而是通过Cache-Control字段设置过期相对时间,也就是多久时间之后过期。
Cache-Control有很多指令,同时既可以在请求头中使用,又可以在响应头中使用。
这里暂且不讨论客户端设置,讲几个常用指令:
cache-control指令:
private设置该字段只能被用户浏览器缓存,不允许代理服务器(CDN)缓存。no-cache使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。no-store设置该字段表示禁止任何缓存max-age设置缓存最大有效期,单位是秒
本blog只关注服务端Cache-Control设置max-age指令,即'Cache-Control':'max-age=20' 表示缓存生存时间是20s,依据接收到资源文件时的客户端时间往后开始计算。
// 省略其他代码fs.readFile(pathname, function (err, data) {res.writeHead(statusCode, {'Content-Type': headType,'Cache-Control':'max-age=20'});res.end(data);});
此时发现资源文件resource.js的响应头有了cache-control字段。
总结:
强缓存能够让客户端不在发送网络请求的情况下获取资源文件
但是,强缓存都是基于时间的,过了时间之后资源文件没有改动也会请求该资源文件,白白浪费带宽和流量。
二、协商缓存
为了解决强缓存的缺点,在HTTP1.1中引入了协商缓存的概念。
last-modified/If-Modified-Since
last-modified 由服务器返回给客户端关于请求资源的最近修改时间 (GMT标准格式)If-Modified-Since 由客户端发送给服务端上次返回的资源修改时间
首次请求时服务端会携带Last-Modified返回给客户端,客户端将其数值保存起来,并重新命名为If-Modified-Since 。
再次请求时,客户端会先发送一个携带If-Modified-Since的请求头发送到服务端,服务端会比较请求头的If-Modified-Since和服务器请求资源上次的修改时间(Last-Modified).
- 如果资源已经被修改:那么返回响应资源resource.js + 响应头,状态码:200 OK
- 如果没有被修改:那么只返回响应头,状态码:304 Not Modified,去本地获取缓存的资源文件。
可以看到,下面两个字段时间戳相同,那么说明文件未被修改,返回304状态码,读取本地缓存。// 省略其他代码// fs.statSync(pathname)返回指定路径文件的文件信息对象,// 对象下mtime属性表示文件上一次修改时间(GMT)let stat = fs.statSync(pathname);fs.readFile(pathname, function (err, data) {// 判断请求头的文件修改时间是否等于服务端的文件修改时间// mtime为文件内容改变的时间戳if(req.headers['if-modified-since'] === stat.mtime.toUTCString()) {statusCode = 304;}res.writeHead(statusCode, {'Content-Type': headType,'Last-Modified':stat.mtime.toUTCString()});res.end(data);});

缺点:
last-modified是以秒为单位的,如果资源在1s内修改多次,由于1s内last-modified并未改变,客户端仍然会使用缓存。
Etag/If-Not-Match
为了解决last-modified的问题,HTTP1.1 还推出了Etag缓存机制,Etag是后端根据文件生成一个标识符。每次改动标识符都不一样,即使再1s内多次改动,也能保证Etag不会重复。ETag (响应值)由服务器返回给客户端根据文件内容,算出的一个唯一的值If-Not-Match (请求值 )由客户端发送给服务端上次返回的资源唯一值
请求流程与Last-Modified一致。
Etag值是由 last_modified 与 content_length 组成
// fs.statSync(pathname)返回指定路径文件的文件信息对象,// 对象下mtime属性表示文件上一次修改时间(GMT)let stat = fs.statSync(pathname);fs.readFile(pathname, function (err, data) {// 定义Etaglet Etag = `${data.length.toString(16)}${stat.mtime.toString(16)}`if(req.headers['if-none-match'] === Etag) {statusCode = 304;}res.writeHead(statusCode, {'Content-Type': headType,Etag});res.end(data);});
三、缓存优先级
先强缓存 再协商缓存
强缓存中Expires和Cache-Control同时存在,Cache-Control优先级高。先用Cache-Control,没有再用Expires
协商缓存Last-modified和Etag同时存在,Etag优先级高,先用Etag,没有再用Last-modified
四、几种不同的刷新方式对缓存影响
- 地址栏回车:走正常流程,本地检查强缓存,强缓存未命中检查则走协商缓存
- 点击刷新、按F5:强缓存直接失效,直接走协商缓存。
- 按Ctrl+F5:所有缓存全部失效,直接重新请求所有资源文件。
参考:
