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");
}