三种缓存方式,不再用麻烦运维小哥哥了!!!

依然在学习node的艰辛过程当中,最近学习了http相关的知识,学到了东西固然第一时间就来和你们分享分享,今天呢就教你们来看看利用node中的http模块去实现不一样的缓存策略!!!javascript

咱们都知道,对于咱们前端开发来讲,缓存是一个十分重要的东西,即但愿用户不能每次请求过来都要重复下载咱们的页面内容,但愿为用户节省流量,而且能提升咱们页面的浏览流畅度,可是同时当咱们修改了一个bug后,又但愿线上可以及时更新,这时候就要求爷爷告奶奶让运维小哥哥帮咱们刷新一下缓存了,那么有没有一些比较好的缓存策略能够针对咱们修改bug又能不麻烦运维及时更新呢,今天咱们就利用node来看一下后端中的缓存策略是如何设置的。css

强制缓存

一般咱们对于强制缓存的设置是服务端告诉客户端你刚刚已经请求过一次了,咱们约定好十分钟内你再过来请求都直接读取缓存吧,意思也就是当客户端在十分钟内屡次请求的话只有第一次会下载页面内容,其余的请求都是直接走缓存,无论咱们页面在这期间有没有变化都不会影响客户端读取缓存。 那咱们来看一下代码的实现html

let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
// 建立一个服务
let server = http.createServer();
// 监听请求
server.on('request',(req,res)=>{
    // 获取到请求的路径
    let {pathname,query} = url.parse(req.url,true);
    // 将路径拼接成服务器上对应得文件路径
    let readPath = path.join(__dirname, 'public',pathname);
    console.log(readPath)
    try {
        // 获取路径状态
        let statObj = fs.statSync(readPath);
        // 服务端设置响应头 Cache-Control 也就是缓存多久以秒为单位
        res.setHeader('Cache-Control','max-age=10');
        // 服务器设置响应头Expires 过时时间 获取当前时间加上刚刚设置的缓存秒数
        res.setHeader('Expires',new Date(Date.now()+10*1000).toGMTString());
        //判断若是路径是一件文件夹 就默认查找该文件下的index.html
        if(statObj.isDirectory()){
            let p = path.join(readPath,'index.html');
            console.log(p);
            // 判断是否有index.html 没有就返回404
            fs.statSync(p);
            // 建立文件可读流 而且pipe到响应res可写流中
            fs.createReadStream(p).pipe(res)
        }else{
            // 若是请求的就是一个文件 那么久直接返回
            fs.createReadStream(readPath).pipe(res)
        }
    } catch (error) {
        // 读取不到 返回404 
        console.log(error)
        res.setHeader('Content-Type','text/html;charset=utf8')
        res.statusCode = 404;
        res.end(`未发现文件`)
    }
})
// 监听3000端口
server.listen(3000)
复制代码

经过上面代码测试咱们会发现当咱们在10秒内进行对同一文件的请求,那么咱们浏览器就会直接走缓存 经过上图能够看到咱们重复请求的时候咱们会看到css变成from memory cache,咱们也看到咱们刚刚的响应头也被设置上了 前端

协商缓存

上面的强制缓存咱们就发现了 就是咱们平时改完bug上线要苦苦等待的一个缘由了,那么有没有其余的好的缓存处理方法呢,咱们设想一下 假如咱们可以知道咱们文件有没有修改,假如咱们修改了服务器就返回最新的内容假如没有修改 就一直默认缓存 ,这样是否是听起来十分的棒!那咱们就想若是咱们可以知道文件的最后修改时间是否是就能够实现了!java

经过文件最后修改时间来缓存

let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let server = http.createServer();
server.on('request',(req,res)=>{
    // 获取到请求的路径
    let {pathname,query} = url.parse(req.url,true);
    // 将路径拼接成服务器上对应得文件路径
    let readPath = path.join(__dirname, 'public',pathname);
    try {
        // 获取路径状态
        let statObj = fs.statSync(readPath);
        // 为了方便测试 咱们告诉客户端不要走强制缓存了
        res.setHeader('Cache-Control','no-cache');
        if(statObj.isDirectory()){
            let p = path.join(readPath,'index.html');
            let statObj = fs.statSync(p);
            // 咱们经过获取到文件状态来拿到文件的最后修改时间 也就是ctime 咱们把这个时间经过响应头Last-Modified来告诉客户端,客户端再下一次请求的时候会经过请求头If-Modified-Since把这个值带给服务端,咱们只要判断这两个值是否相等,假如相等那么也就是说 文件没有被修改那么咱们就告诉客户端304 你直接读缓存吧
            res.setHeader('Last-Modified',statObj.ctime.toGMTString());
            if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
                res.statusCode = 304;
                res.end();
                return
            }
            // 修改了那么咱们就直接返回新的内容
            fs.createReadStream(p).pipe(res)
        }else{
            res.setHeader('Last-Modified',statObj.ctime.toGMTString());
            if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
                res.statusCode = 304;
                res.end();
                return
            }
            fs.createReadStream(readPath).pipe(res)
        }
    } catch (error) {
        console.log(error)
        res.setHeader('Content-Type','text/html;charset=utf8')
        res.statusCode = 404;
        res.end(`未发现文件`)
    }
})

server.listen(3000)
复制代码

咱们经过请求能够看到,当咱们第一次请求事后,不管怎么刷新请求都是304 直接读取的缓存,假如咱们在服务端把这个文件修改了 那么咱们就能看到又能请求到最新的内容了,这就是咱们经过协商缓存来处理的,咱们经过获取到文件状态来拿到文件的最后修改时间 也就是ctime 咱们把这个时间经过响应头Last-Modified来告诉客户端,客户端再下一次请求的时候会经过请求头If-Modified-Since把这个值带给服务端,咱们只要判断这两个值是否相等,假如相等那么也就是说 文件没有被修改那么咱们就告诉客户端304 你直接读缓存吧

经过文件内容来缓存

再再再再再假如咱们在文件中删除了字符a而后又还原了,那么这时候保存咱们的文件的修改时间其实也发生了变化,可是其实咱们文件的真正内容并无发生变化,因此这时候其实客户端继续走缓存也是能够的 ,咱们来看看这样的缓存策略如何实现。node

let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let crypto = require('crypto');
let server = http.createServer();
server.on('request',(req,res)=>{
    // 获取到请求的路径
    let {pathname,query} = url.parse(req.url,true);
    // 将路径拼接成服务器上对应得文件路径
    let readPath = path.join(__dirname, 'public',pathname);
    try {
        // 获取路径状态
        let statObj = fs.statSync(readPath);
        // 为了方便测试 咱们告诉客户端不要走强制缓存了
        res.setHeader('Cache-Control','no-cache');
        if(statObj.isDirectory()){
            let p = path.join(readPath,'index.html');
            let statObj = fs.statSync(p);
            // 咱们经过流把文件读取出来 而后对读取问来的内容进行md5加密 获得一个base64加密hash值
            let rs = fs.createReadStream(p);
            let md5 = crypto.createHash('md5');
            let arr = [];
            rs.on('data',(data)=>{
                arr.push(data);
                md5.update(data);
            })
            rs.on('end',(data)=>{
                let r = md5.digest('base64');
                // 而后咱们将这个hash值经过响应头Etag传给客户端,客户端再下一次请求的时候会把上一次的Etag值经过请求头if-none-match带过来,而后咱们就能够继续比对文件生成的hash值和上次产生的hash是否同样 若是同样说明文件内容没有发生变化 就告诉客户端304 读取缓存
                res.setHeader('Etag',r);
                if(req.headers['if-none-match']===r){
                    res.statusCode=304;
                    res.end();
                    return;
                }
                res.end(Buffer.concat(arr))
            })
        }else{
            let rs = fs.createReadStream(readPath);
            let md5 = crypto.createHash('md5');
            let arr = [];
            rs.on('data',(data)=>{
                arr.push(data);
                md5.update(data);
            })
            rs.on('end',(data)=>{
                let r = md5.digest('base64');
                res.setHeader('Etag',r);
                if(req.headers['if-none-match']===r){
                    res.statusCode=304;
                    res.end();
                    return;
                }
                res.end(Buffer.concat(arr))
            })
        }
    } catch (error) {
        console.log(error)
        res.setHeader('Content-Type','text/html;charset=utf8')
        res.statusCode = 404;
        res.end(`未发现文件`)
    }
})

server.listen(3000)
复制代码

经过控制台咱们能够看出来 请求头和响应头中都有咱们上面所说的对应的值,可是从代码里咱们也能看出来,咱们每次在请求到来的时候都会把文件所有读取出来而且进行加密生产hash而后再作对比,这样其实十分的消耗性能,所以这种缓存方式也有他本身的缺点后端

总结

咱们经过node来亲自实现了三种缓存方式,咱们能够总结出每种缓存方式对应的实现:浏览器

  • 强制缓存 服务端设置响应头Cache-Control:max-age=xxx,而且设置Expires响应头过时时间,客户端自行判断是否读取缓存
  • 协商缓存 经过状态码304告诉客户端该走缓存
    • 修改时间:经过文件的最后修改时间判断该不应读取缓存,服务端设置响应头Last-Modified,客户端把上次服务端响应头中的Last-modified值经过if-modified-since 传递给服务端 , 服务端经过比较当前文件的修改时间和上次修改时间(上次传给客户端的值),若是相等那么说明文件修改时间没变也就是没变化
    • 文件内容:经过文件的内容来判断该不应读取缓存,服务端经过把文件内容读取出来,经过md5进行base64加密得出hash值,把这个值设置响应头Etag,客户端下一次请求经过if-none-match带过来,服务端再比对当前文件内容加密得出的hash值和上次是否同样,若是同样说明文件内容没有发生改变,这种方式是最准确的方式,可是也是最耗性能
相关文章
相关标签/搜索