学习笔记-http缓存机制

本文详细介绍了浏览器的缓存机制,包括缓存规则中的三个角色、三级缓存机制(内存和磁盘缓存)、强缓存与对比缓存的原理,以及缓存判断的优先级。还探讨了相关HTTP头部字段的作用,如`cache-control`、`expires`、`if-modified-since`、`if-none-match`等,并给出了实际应用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浏览器缓存机制

缓存规则

三个角色

  • 客户端
  • 缓存数据库
  • 中间服务器+源服务器

三级缓存机制

from memory cache

不请求网络资源,资源在内存当中,一般字体、图片会存在内存当中

不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当kill进程后,也就是浏览器关闭以后,数据将不存在。

statusCode:200

from disk cache

不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等。

不访问服务器,直接读缓存,从磁盘中读取缓存,当kill进程时,数据还是存在。

statusCode:200

请求网络资源

访问服务期,请求相应的资源

  • 未过期:不返回新资源,statusCode:304
  • 已过期:返回新资源,statusCode:200

缓存校验

强缓存

请求的资源被标记为

强制缓存涉及到的请求头有:cache-control、expires (有这些请求/响应头的资源被标记为强缓存

强制缓存涉及到的响应头有:cache-control、expires

只要请求一次,在有效期内

  • expires :标记过期时间,格式为绝对时间 (缺陷:客户端时间与服务器时间可能不同步,不能保证资源的新鲜度

请求头

  • cache-control:
    • no-cache:告诉中间服务器:我不要过期资源,给我去源服务器去取!
    • no-store:告诉中间服务器:不要缓存我的请求和响应
    • max-age

响应头

  • cache-control:
    • no-cache:不要缓存过期资源
    • no-store:不要缓存请求或响应的任何内容
    • must-revalidate:客户端必须再次向源服务器确认资源的有效性
    • max-age:资源最大的有效期
    • private:只能客户端缓存
    • public:客户端和中间服务器都可以缓存

对比缓存

对比缓存涉及到的请求头有:last-modified-since、if-none-match (有这些请求/响应头的资源被标记为对比缓存

对比缓存涉及到的响应头有:Last-Modified、etag

无论资源是否过期(对比缓存也没法判断是否过期啊),都会向服务器发起校验请求

服务器校验后发现客户端缓存的资源已过期,则返回新资源,响应码为200

服务器校验后发现客户端缓存的资源没有过期,则返回响应码为304

缓存判断优先级

  • cache-control和expires同时存在时,cache-control优先级高于expires

  • 对于对比缓存,同时有etag和last-modified时etag优先级更高(last-modified是一个绝对时间,而时间可能不准,etag一般是md5值)

示例


  • 前端主要代码
  <script>
    const logDom = document.getElementById('log')
    const btn = document.getElementById('btn_log')
    btn.onclick = function () {
      const xhr = new XMLHttpRequest()
      xhr.open('get', 'http://localhost:8081/log') 
      // xhr.setRequestHeader('cache-control','min-fresh=10');
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          log.innerText =(new Date()).toLocaleString()+'\r\n'+ xhr.responseText
        }
      }
      xhr.send()
    }

  </script>

  • if-modified-since/last-modified
function onRequest(req, res) {
  let { pathname } = url.parse(req.url, true)
  if (pathname === '/log') {
    let logPath = path.join(__dirname, 'log.txt')
    access(logPath).then(_ => {
      stat(logPath).then(stats => {
        let ifModifiedSince = req.headers['if-modified-since'];
       console.log(`ifModifiedSince ${chalk.yellow(ifModifiedSince)}`)
        let lastModified = stats.ctime.toGMTString();
       console.log(`lastModified ${chalk.red(lastModified)}`)

        if (ifModifiedSince == lastModified) {
            res.writeHead(304);
            res.end('');
        } else {
            return sendFile(req, res, logPath, stats);
        }

      }).catch(err => {
        sendError(req, res, 500, '', util.inspect(err))
      })
    }).catch(err => {
      sendError(req, res, 500, '', util.inspect(err))
    })

  }
  else {
    sendError(req, res, 404, 'NOT FOUND')
  }
}

function sendFile(req, res, filepath,stat) {
  res.setHeader('Access-Control-Allow-Origin','*')
  res.setHeader('Content-Type', mime.getType(filepath));
  //发给客户端之后,客户端会把此时间保存起来,下次再获取此资源的时候会把这个时间再发回服务器
  res.setHeader('Last-Modified', stat.ctime.toGMTString());
  fs.createReadStream(filepath).pipe(res);
}


  • If-None-Match/etag

function onRequest(req, res) {
  let { pathname } = url.parse(req.url, true)
  if (pathname === '/log') {
    let logPath = path.join(__dirname, 'log.txt')
    access(logPath).then(_ => {
      stat(logPath).then(stats => {
        // 坑爹的货色 headers的key都是小写的
        let ifNoneMatch = req.headers['If-None-Match'.toLowerCase()];

        console.log(`If-None-Match ${chalk.yellow(ifNoneMatch)}`)
        let out = fs.createReadStream(logPath);
        let md5 = crypto.createHash('md5');

        out.on('data', function (data) {
            md5.update(data);
        });
        out.on('end', function () {
            //1.相同的输入相同的输出 2 不同的输入不同的输入 3 不能从输出反推出输入 
            let etag = md5.digest('hex');
            console.log(`If-None-Match ${chalk.yellow(etag)}`)
            // let etag = `${stat.size}`;
            if (ifNoneMatch == etag) {
                res.writeHead(304);
                res.end('');
            } else {
                return sendFile(req, res, logPath, etag);
            }
        });

      }).catch(err => {
        sendError(req, res, 500, '', util.inspect(err))
      })
    }).catch(err => {
      sendError(req, res, 500, '', util.inspect(err))
    })

  }
  else {
    sendError(req, res, 404, 'NOT FOUND')
  }
}

function sendFile(req, res, filepath,etag) {
  res.setHeader('Access-Control-Allow-Origin','*')
  res.setHeader('Content-Type', mime.getType(filepath));
  //发给客户端之后,客户端会把此时间保存起来,下次再获取此资源的时候会把这个时间再发回服务器
  res.setHeader('etag', etag);
  fs.createReadStream(filepath).pipe(res);
}

  • cache-control
function onRequest(req, res) {
  let { pathname } = url.parse(req.url, true)
  if (pathname === '/log') {
    let logPath = path.join(__dirname, 'log.txt')
    access(logPath).then(_ => {
      stat(logPath).then(stats => {
        sendFile(req, res, logPath,stats);
      }).catch(err => {
        sendError(req, res, 500, '', util.inspect(err))
      })
    }).catch(err => {
      sendError(req, res, 500, '', util.inspect(err))
    })

  }
  else {
    sendError(req, res, 404, 'NOT FOUND')
  }
}

function sendFile(req, res, filepath,stats) {
  res.setHeader('Access-Control-Allow-Origin','*')
  res.setHeader('Access-Control-Allow-Headers','cache-control')
  res.setHeader('Content-Type', mime.getType(filepath));
  //发给客户端之后,客户端会把此时间保存起来,下次再获取此资源的时候会把这个时间再发回服务器
  res.setHeader('Cache-Control', 'max-age=10,must-revalidate');
  // res.setHeader('Cache-Control', 'max-age=10');
  res.setHeader('Last-Modified', stats.ctime.toGMTString());
  fs.createReadStream(filepath).pipe(res);
}

总结

相关header

  • expires
  • cache:no-cache,no-store,max-age,must-revalidate,private,public
  • if-modified-since/last-modified
  • if-none-match/etag

相关响应吗

  • 304 资源未修改
  • 200
  • 流程图:

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值