1. 缓存的作用
- 缩小网络流量传输,减少页面渲染速度
- 缩小服务器累赘,进步网站性能能
- 减少网站加载速度,进步网站性能
2. 缓存的分类
比照缓存
浏览器第一次申请,服务器会把资源内容和缓存标识一块返回客户端,客户端将二者备份至缓存数据库中。
当前每次应用资源,都须要带着缓存标识申请服务器。服务器依据申请携带的缓存标识,判断资源是否过期,如果过期间接返回资源内容和新的缓存标识,如果不过期,返回状态码 304,告诉客户端没有生效能够应用缓存数据库外面的数据。
- 比照缓存,缓存未生效
- 比照缓存 缓存生效
缓存标识
最初批改工夫
- 服务端响应的时候, 在响应头中减少
Last-Modified
,通知客户端此资源的最初批改工夫 If-Modified-Since
:在浏览器再次发动申请是,发现资源有Last-Modified
属性,会主动在申请头中减少If-Modified-Since
。值为 Last-Modified 的值- 服务器收到申请后发现有头 If-Modified-Since 则与被申请资源的最初批改工夫进行比对。若最初批改工夫较新,阐明资源又被改变过,则响应最新的资源内容并返回 200 状态码;
- 若最初批改工夫和 If-Modified-Since 一样,阐明资源没有批改,则响应 304 示意未更新,告知浏览器持续应用所保留的缓存文件。
- Last-Modified 存在的问题
- 某些服务器不能准确的失去资源的最初批改工夫,这样就无奈通过最初批改工夫更新
- 对于常常批改的资源,在秒级以下的工夫更新,Last-Modified 的更新工夫只能准确到秒
- 一些文件的最初批改工夫扭转了,然而内容并未扭转。咱们不心愿客户端认为这个文件批改了。
- 如果同样的一个文件位于多个 CDN 服务器上的时候内容尽管一样,批改工夫不一样。
- 代码实现
let http = require('http');
let fs = require('fs');
let path = require('path');
let mime = require('mime');
http.createServer(function (req, res) {let file = path.join(__dirname, req.url);
fs.stat(file, (err, stat) => {if (err) {sendError(err, req, res, file, stat);
} else {let ifModifiedSince = req.headers['if-modified-since'];
if (ifModifiedSince) {if (ifModifiedSince == stat.ctime.toGMTString()) {res.writeHead(304);
res.end();} else {send(req, res, file, stat);
}
} else {send(req, res, file, stat);
}
}
});
}).listen(8080);
function send(req, res, file, stat) {res.setHeader('Last-Modified', stat.ctime.toGMTString());
res.writeHead(200, { 'Content-Type': mime.getType(file) });
fs.createReadStream(file).pipe(res);
}
function sendError(err, req, res, file, stat) {res.writeHead(400, { "Content-Type": 'text/html'});
res.end(err ? err.toString() : "Not Found");
}
- ETag
ETag 是依据实体内容生成的一段 hash 字符串, 能够作为资源的内容的惟一示意。当资源产生扭转时,ETag 也随之发生变化。ETag 是 Web 服务端产生的,而后发给浏览器客户端。
- 客户端想判断缓存是否可用能够先获取缓存中文档的 ETag,而后通过
If-None-Match
发送申请给 Web 服务器询问此缓存是否可用。 - 服务器收到申请,将服务器的中此文件的 ETag, 跟申请头中的
If-None-Match
相比拟, 如果值是一样的, 阐明缓存还是最新的,Web 服务器将发送 304 Not Modified 响应码给客户端示意缓存未修改过,能够应用。 - 如果不一样则 Web 服务器将发送该文档的最新版本给浏览器客户端
- Etag 的毛病
- 服务器每次都要计算资源的 hash 值,耗费服务器资源
- 代码实现
let http = require('http');
let fs = require('fs');
let path = require('path');
let mime = require('mime');
let crypto = require('crypto');
http.createServer(function (req, res) {let file = path.join(__dirname, req.url);
fs.stat(file, (err, stat) => {if (err) {sendError(err, req, res, file, stat);
} else {let ifNoneMatch = req.headers['if-none-match'];
let etag = crypto.createHash('sha1').update(stat.ctime.toGMTString() + stat.size).digest('hex');
if (ifNoneMatch) {if (ifNoneMatch == etag) {res.writeHead(304);
res.end();} else {send(req, res, file, etag);
}
} else {send(req, res, file, etag);
}
}
});
}).listen(8080);
function send(req, res, file, etag) {res.setHeader('ETag', etag);
res.writeHead(200, { 'Content-Type': mime.lookup(file) });
fs.createReadStream(file).pipe(res);
}
function sendError(err, req, res, file, etag) {res.writeHead(400, { "Content-Type": 'text/html'});
res.end(err ? err.toString() : "Not Found");
}
强制缓存
强制缓存,在本地缓存未生效状况下,间接应用本地缓存,不进行与服务端交互。
- 缓存未生效
- 缓存生效
缓存标识
浏览器会将文件缓存到 Cache 目录,第二次申请时浏览器会先查看 Cache 目录下是否含有该文件,如果有,并且还没到 Expires 设置的工夫,即文件还没有过期,那么此时浏览器将间接从 Cache 目录中读取文件,而不再发送申请
- Expires是服务器响应音讯头字段,在响应 http 申请时通知浏览器在过期工夫前浏览器能够间接从浏览器缓存取数据,而无需再次申请, 这是 HTTP1.0 的内容,当初浏览器均默认应用 HTTP1.1, 所以根本能够疏忽
- Cache-Control与 Expires 的作用统一,都是指明以后资源的有效期,管制浏览器是否间接从浏览器缓存取数据还是从新发申请到服务器取数据, 如果同时设置的话,其优先级高于Expires
- private 客户端能够缓存
- public 客户端和代理服务器都能够缓存
- max-age=60 缓存内容将在 60 秒后生效
- no-cache 须要应用比照缓存验证数据, 强制向源服务器再次验证
- no-store 所有内容都不会缓存,强制缓存和比照缓存都不会触发
Cache-Control:private, max-age=60, no-cache
代码实现
let http = require('http');
let fs = require('fs');
let path = require('path');
let mime = require('mime');
let crypto = require('crypto');
http.createServer(function (req, res) {let file = path.join(__dirname, req.url);
console.log(file);
fs.stat(file, (err, stat) => {if (err) {sendError(err, req, res, file, stat);
} else {send(req, res, file);
}
});
}).listen(8080);
function send(req, res, file) {let expires = new Date(Date.now() + 60 * 1000);
res.setHeader('Expires', expires.toUTCString());
res.setHeader('Cache-Control', 'max-age=60');
res.writeHead(200, { 'Content-Type': mime.lookup(file) });
fs.createReadStream(file).pipe(res);
}
function sendError(err, req, res, file, etag) {res.writeHead(400, { "Content-Type": 'text/html'});
res.end(err ? err.toString() : "Not Found");
}