乐趣区

深入node.js-浏览器缓存机制

浏览器缓存
浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。
浏览器缓存的使用是提高用户体验的一个重要途径,通常也是优化前端的一种重要方式。利用好了缓存可以加快页面的浏览,降低服务器的压力,减少网络损耗等功能。
浏览器缓存分类

协商缓存
强制缓存

协商缓存
通过上图分析:

客户端向服务器请求资源
验证标识,如果标识通过了验证,则会响应 304,告知浏览器读取缓存
如果没有标识,或验证没有通过,则返回请求的资源

看到这里可能有人会有问题,标识是什么?标识主要是用来标识请求的资源是否被修改或更新过,通过请求头发送给服务器进行验证。
协商缓存的标识有两种:

ETag
Last-Modified

下面我们来讲讲这两者的区别以及用法
Last-Modified
last-modified 根据词义就可以知道表示该资源的最后修改时间。

客户端第一次请求服务器,服务器会把该资源的最后修改时间通过响应头返回给客户端
客户端再次请求服务器的时候,如果在响应头中有 Last-Modified 字段,浏览器就会在请求头中加上 if-Modified-Since 字段给服务器。
服务器拿到该字段的值,与该资源的最后修改时间进行对比,如果相等则说明资源没有被修改,向客户端返回 304。
浏览器看到 304 就会去读取缓存信息并呈现。

下面根据以上的几个点,来看看代码怎么实现:
const http = require(‘http’);
const url = require(‘url’);
const path = require(‘path’);
const fs = require(‘fs’);
const mime = require(‘mime’);

const server = http.createServer((req, res) => {
// 获取文件名
const {pathname} = url.parse(req.url, true);
// 获取文件路径
const filepath = path.join(__dirname, pathname);

/**
* 判断文件是否存在
*/
fs.stat(filepath, (err, stat) => {
if (err) {
res.end(‘not found’);
} else {
// 获取 if-modified-since 这个请求头
const ifModifiedSince = req.headers[‘if-modified-since’];
// 获取资源最后修改时间
let lastModified = stat.ctime.toGMTString();
// 验证资源是否被修改过,如果相同则返回 304 让浏览器读取缓存
if (ifModifiedSince === lastModified) {
res.writeHead(304);
res.end();
}
// 缓存没有通过则返回资源,并加上 last-modified 响应头,下次浏览器就会在请求头中带着 if-modified-since
else {
res.setHeader(‘Content-Type’, mime.getType(filepath));
res.setHeader(‘Last-Modified’, stat.ctime.toGMTString());
fs.createReadStream(filepath).pipe(res);
}
}
});
});

server.listen(8000, () => {
console.log(‘listen to 8000 port’);
});

ETag

ETag 它的流程和 last-modified 是一样的,仅仅只是验证方式不同,last-modified 是取的当前请求资源的最后修改时间来作为验证,而 ETag 则是对当前请求的资源做一个唯一的标识。
标识可以是一个字符串,文件的 size,hash 等等,只要能够合理标识资源的唯一性并能验证是否修改过就可以了。比如读取文件内容,将文件内容转换成一个 hash 值,每次接收到客户端发送过来的时候,重新读取文件转成 hash 值,与之前的做对比,看资源是否修改过。
和 Last-Modify 相同,服务器在响应头返回一个 ETag 字段,那么请求的时候就会在请求头中加入 if-none-match

下面来看看代码,代码中我都会加入详细的注释:
const http = require(‘http’);
const url = require(‘url’);
const path = require(‘path’);
const fs = require(‘fs’);
const mime = require(‘mime’);
const crypto = require(‘crypto’);

const server = http.createServer(function(req, res) {
// 获取请求的资源名称
let {pathname} = url.parse(req.url, true);
// 获取文件路径
let filepath = path.join(__dirname, pathname);

/**
* 判断文件是否存在
*/
fs.stat(filepath, (err, stat) => {
if (err) {
return sendError(req, res);
} else {
let ifNoneMatch = req.headers[‘if-none-match’];
let readStream = fs.createReadStream(filepath);
let md5 = crypto.createHash(‘md5’);

// 通过流的方式读取文件并且通过 md5 进行加密,相当于转成一个 hash 字符串作为 etag 的值
readStream.on(‘data’, function(data) {
md5.update(data);
});
readStream.on(‘end’, function() {
let etag = md5.digest(‘hex’);
// 验证 etag,判断资源是否被修改过,如果没有则返回 304
if (ifNoneMatch === etag) {
res.writeHead(304);
res.end();
} else {
res.setHeader(‘Content-Type’, mime.getType(filepath));
// 第一次服务器返回的时候,会把文件的内容算出来一个标识,发给客户端
fs.readFile(filepath, (err, content) => {
// 客户端看到 etag 之后,也会把此标识保存在客户端,下次再访问服务器的时候,发给服务器
res.setHeader(‘Etag’, etag);
fs.createReadStream(filepath).pipe(res);
});
}
});
}
});
});
server.listen(8000, () => {
console.log(‘listen to 8000 port’);
});
强制缓存

通过上图分析:

强制缓存通过 Cache-Control 这个响应头中的 max-age:60(缓存 60s) 来判断缓存是否过期
如果过期了则重新向服务器请求资源
如果没有过期,则不经过服务器,直接读取资源

强制缓存比较简单,直接看一下代码的实现
const http = require(‘http’);
const url = require(‘url’);
const path = require(‘path’);
const fs = require(‘fs’);
const mime = require(‘mime’);

const server = http.createServer(function(req, res) {
let {pathname} = url.parse(req.url, true);
let filepath = path.join(__dirname, pathname);
fs.stat(filepath, (err, stat) => {
if (err) {
res.setHeader(‘Content-Type’, mime.getType(filepath));
// 设置缓存过期时间
res.setHeader(‘Cache-Control’, ‘max-age=100’);
fs.createReadStream(filepath).pipe(res);
} else {
return send(req, res, filepath);
}
});
});
server.listen(8000, () => {
console.log(‘listen to port 8000’);
});
强制缓存就是向浏览器设置一个过期时间例如 cache-control:max-age=60 表示这是一个 60 秒的过期时间,60 秒以内浏览器都会从缓存读取该资源,超过 60 秒则访问服务器
cache-control 还有另外几个值可以设置

private 客户端可以缓存
public 客户端和代理服务器都可以缓存
max-age=60 缓存内容将在 60 秒后失效
no-cache 需要使用对比缓存验证数据, 强制向源服务器再次验证
no-store 所有内容都不会缓存,强制缓存和对比缓存都不会触发

总结
理解缓存对前端开发来说十分的重要,这也是为何把这篇文章写出来的原因,后续会继续为大家带来 node 相关的文章,如果写错或不好的地方希望大家指出来,如果觉得写的还行,麻烦点个赞哈!

退出移动版