缓存的作用
- 减少了冗余数据的传输,节省了网费。
- 减少了服务器的负担,提高网站的性能
缓存分为强制缓存和协商缓存
- 强缓存如果生效不需要再和服务器发生交互,对比缓存不管生不生效都需要与服务端发生交互
- 两类缓存可以同时存在,强缓存的优先级高于对比缓存
1.对比缓存(协商缓存)
- 通过最后修改时间来判断缓存是否可用(Last-Modified):
/**
* 1.第一次访问服务器的时候,服务器会返回资源和缓存的标识,客户端则会把此资源缓存在本地的缓存数据库中。
* 2.第二次客户端需要此数据的时候,要取得缓存的标识,然后去问下服务器我的资源是否是最新的。
* 如果是最新的则直接使用缓存数据,如果不是最新的则服务器返回新的资源和缓存规则,客户端根据新的规则缓存新的数据
*/
const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("fs");
const mime = require("mime");
http.createServer(function(req,res){
const { pathname } = url.parse(req.url,true);
const filepath = path.join(__dirname,pathname);
fs.stat(filepath,(err,stat)=>{
if(err){
return sendErr(req,res)
}else{
const ifModifiedSince = req.headers['if-modified-since'];
const LastModified = stat.ctime.toGMTString();
if(ifModifiedSince == LastModified){
res.writeHead(304);
res.end();
}else{
return send(req,res,filepath,stat)
}
}
})
}).listen(8080);
function sendErr(req,res){
res.end('Not Found');
}
function send(req,res,filepath,stat){
res.setHeader("Content-Type",mime.getType(filepath));
//把文件的最后修改时间发给客户端,客户端会把此时间保存起来 ,次再获取此资源的时候会把这个时间再发给服务器
res.setHeader("Last-Modified",stat.ctime.toGMTString());
fs.createReadStream(filepath).pipe(res);
}
该方法存在的问题:
某些服务器拿不到文件的最后修改时间,无法判断
某些文件修改的非常频繁,在秒以下的时间内进行修改 ,Last-Modified 只能精确到秒
一些文件修改时间改了,但内容没变,我们不希望客户端认为这个文件修改了
同一个文件处于多个CDN服务器上的时候,内容虽然一样但最后修改时间不一样
2.Etag (协商缓存)
Etag是实体标签的缩写,根据实体内容生成一段hash字符串,可以标识资源的状态,资源发生改变时Etag也随之发生改变,ETag由服务端产生发给客户端。
- 第一次服务器返回的时候,会把文件的内容算出来一个标识(通常是md5值),发给客户端,
- 客户端看到Etag的时候,会把此标识符存在客户端,下次再访问服务器的时候,发给服务器
- 服务器根据两次的值对比判断是否返回新的内容。
const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("fs");
const crypto = require("crypto");
const mime = require("mime");
http.createServer(function(req,res){
const { pathname } = url.parse(req.url,true);
const filepath = path.join(__dirname,pathname);
fs.stat(filepath,(err,stat)=>{
if(err){
return sendErr(req,res)
}else{
const ifNoneMatch = req.headers['if-none-match'];
const out = fs.createReadStream(filepath);
const md5 = crypto.createHash("md5");
out.on("data",function(data){
md5.update(data)
})
out.on("end",function(data){
/**
* mad5 :
* 相同的输入 相同的输出。
* 不同的输入不同的输出。
* 不能从输出反推输入
*/
const Etag = md5.digest("hex");
if(ifNoneMatch == Etag){
res.writeHead(304);
res.end();
}else{
return send(req,res,filepath,Etag)
}
})
}
})
}).listen(8080);
function sendErr(req,res){
res.end('Not Found');
}
function send(req,res,filepath,Etag){
res.setHeader("Content-Type",mime.getType(filepath));
//第一次服务器返回的时候,会把文件的内容算出来一个标识,发给客户端,
//客户端看到Etag的时候,会把此标识符存在客户端,下次再访问服务器的时候,发给服务器
res.setHeader("Etag",Etag);
fs.createReadStream(filepath).pipe(res);
}
强缓存
- 把资源缓存在客户端,如果客户端需要再次使用此资源的时候,先获取到缓存中的数据,看是否过期 如果过期了再请求服务器
Cache-Control的值
no-cache : 不使用强缓存
no-store: 强缓存和协商缓存都不使用
关键代码 ,在响应头中设置
res.setHeader("Cache-Control","max-age=30");
具体实现
/**
* 强缓存
* 把资源缓存在客户端,如果客户端需要再次使用此资源的时候,先获取到缓存中的数据,看是否过期
* 如果过期了再请求服务器。
*/
const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("fs");
const mime = require("mime");
http.createServer(function(req,res){
const { pathname } = url.parse(req.url,true);
const filepath = path.join(__dirname,pathname);
fs.stat(filepath,(err,stat)=>{
if(err){
return sendErr(req,res)
}else{
return send(req,res,filepath,stat)
}
})
}).listen(8080);
function sendErr(req,res){
res.end('Not Found');
}
function send(req,res,filepath,stat){
res.setHeader("Content-Type",mime.getType(filepath));
res.setHeader("Cache-Control","max-age=30");
fs.createReadStream(filepath).pipe(res);
}