为了减少数据传输,减少请求数,继续添加缓存支持。首先梳理一下缓存的处理流程:
- 如果是第一次访问,请求报文首部不会包含相关字段,服务端在发送文件前做如下处理:
- 设置
Expires
头 - 设置
Cache-Control
头(设置其max-age
值) - 如服务器支持
Last-Modified
,设置Last-Modified
头 - 如服务器支持
ETag
,设置ETag
头
浏览器收到响应后会存下这些标记,并在下次请求时带上与
ETag
对应的请求首部If-None-Match
或与Last-Modified
对应的请求首部If-Modified-Since
。 - 设置
- 如果是重复的请求:
- 浏览器判断缓存是否过期(通过
Cache-Control
和Expires
确定)- 如果未过期,直接使用缓存内容,也就是强缓存命中,并不会产生新的请求
- 如果已过期,会发起新的请求,并且请求会带上
If-None-Match
或If-Modified-Since
,或者兼具两者 - 服务器收到请求,进行缓存的新鲜度再验证:
- 首先检查请求是否有
If-None-Match
首部,没有则继续下一步,有则将其值与文档的最新ETag匹配,失败则认为缓存不新鲜,成功则继续下一步 - 接着检查请求是否有
If-Modified-Since
首部,没有则保留上一步验证结果,有则将其值与文档最新修改时间比较验证,失败则认为缓存不新鲜,成功则认为缓存新鲜
当两个首部皆不存在或者验证结果是不新鲜时,发送200及最新文件,并在首部更新新鲜度。
当验证结果是缓存仍然新鲜时(也就是弱缓存命中),不需发送文件,仅发送304,并在首部更新新鲜度
- 首先检查请求是否有
- 浏览器判断缓存是否过期(通过
添加缓存配置
cache: { //配置服务器支持的缓存情况
maxAge: 600, //服务器认为自己文件有效的秒数,也就是说10分钟内这个文件就用本地缓存的
expires: true, //Expires 响应头包含日期/时间, 即在此时候之后,响应过期。如果在Cache-Control响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略。
cacheControl: true, //是否支持CacheControl
lastModified: true, //是否支持last modified
etag: true //是否支持etag
}
缓存工具函数
const {cache} = require('../config/defaultConfig');
//根据stat生成ETag
function generateETag(stat) {
const mtime = stat.mtime.getTime().toString(16);
const size = stat.size.toString(16);
return `W/"${size}-${mtime}"`;
}
function refreshRes(stats, res) {
const {maxAge, expires, cacheControl, lastModified, etag} = cache;
if(expires) {
// Expires 响应头包含日期/时间, 即在此时候之后,响应过期。
// 无效的日期,比如 0, 代表着过去的日期,即该资源已经过期。
res.setHeader('Expires', (new Date(Date.now() + maxAge*1000)).toUTCString());
}
if(cacheControl) {
res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
}
if(lastModified) {
//stats.mtime表示修改时间
res.setHeader('Last-Modified', stats.mtime.toUTCString());
}
if(etag) { //添加etag
res.setHeader('ETag', generateETag(stats));
}
}
module.exports = function isFresh(stats, req, res) {
//为返回的结果【res】添加缓存信息
refreshRes(stats, res);
//判断请求【req】是否让客户端继续使用缓存
const lastModified = req.headers['if-modified-since'];
const etag = req.headers['if-none-match'];
//如果这两个信息都没有,说明他很有可能是第一次请求
if(!lastModified && !etag) {
return false;
}
// 如果有lastModified,并且宇设置的lastModified不一样,说明也过期了
if(lastModified && lastModified !== res.getHeader('Last-Modified')) {
return false;
}
//检测etag
if(etag && etag !== res.getHeader('ETag')) {
return false;
}
return true; //表示让客户端继续用缓存
};
在返回资源之前进行判断
//判断缓存是否可用
if(isFresh(stats, req, res)) {
res.statusCode = 304;
res.end();
return;
}