深刻node.js-浏览器缓存机制

浏览器缓存

浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就能够从本地磁盘显示文档,这样就能够加速页面的阅览。

浏览器缓存的使用是提升用户体验的一个重要途径,一般也是优化前端的一种重要方式。利用好了缓存能够加快页面的浏览,下降服务器的压力,减小网络损耗等功能。图片描述前端

浏览器缓存分类

  • 协商缓存
  • 强制缓存

协商缓存

经过上图分析:node

  • 客户端向服务器请求资源
  • 验证标识,若是标识经过了验证,则会响应304,告知浏览器读取缓存
  • 若是没有标识,或验证没有经过,则返回请求的资源

看到这里可能有人会有问题,标识是什么?
标识主要是用来标识请求的资源是否被修改或更新过,经过请求头发送给服务器进行验证。浏览器

协商缓存的标识有两种:缓存

  • ETag
  • Last-Modified

下面咱们来说讲这二者的区别以及用法服务器

Last-Modified

last-modified 根据词义就能够知道表示该资源的最后修改时间。微信

  • 客户端第一次请求服务器,服务器会把该资源的最后修改时间经过响应头返回给客户端
  • 客户端再次请求服务器的时候,若是在响应头中有Last-Modified字段,浏览器就会在请求头中加上if-Modified-Since字段给服务器。
  • 服务器拿到该字段的值,与该资源的最后修改时间进行对比,若是相等则说明资源没有被修改,向客户端返回304。
  • 浏览器看到304就会去读取缓存信息并呈现。

下面根据以上的几个点,来看看代码怎么实现:网络

const http = require('http');
    const url = require('url');
    const path = require('path');
    const fs = require('fs');
    const mime = require('mime');
    
    const server = http.createServer((req, res) => {
      // 获取文件名
      const { pathname } = url.parse(req.url, true);
      // 获取文件路径
      const filepath = path.join(__dirname, pathname);
    
      /**
       * 判断文件是否存在
       */
      fs.stat(filepath, (err, stat) => {
        if (err) {
          res.end('not found');
        } else {
          // 获取if-modified-since这个请求头
          const ifModifiedSince = req.headers['if-modified-since'];
          // 获取资源最后修改时间
          let lastModified = stat.ctime.toGMTString();
          // 验证资源是否被修改过,若是相同则返回304让浏览器读取缓存
          if (ifModifiedSince === lastModified) {
            res.writeHead(304);
            res.end();
          }
          // 缓存没有经过则返回资源,并加上 last-modified响应头,下次浏览器就会在请求头中带着 if-modified-since
          else {
            res.setHeader('Content-Type', mime.getType(filepath));
            res.setHeader('Last-Modified', stat.ctime.toGMTString());
            fs.createReadStream(filepath).pipe(res);
          }
        }
      });
    });
    
    server.listen(8000, () => {
      console.log('listen to 8000 port');
    });

ETag

  • ETag它的流程和last-modified是同样的,仅仅只是验证方式不一样,last-modified是取的当前请求资源的最后修改时间来做为验证,而ETag则是对当前请求的资源作一个惟一的标识。
  • 标识能够是一个字符串,文件的size,hash等等,只要可以合理标识资源的惟一性并能验证是否修改过就能够了。好比读取文件内容,将文件内容转换成一个hash值,每次接收到客户端发送过来的时候,从新读取文件转成hash值,与以前的作对比,看资源是否修改过。
  • 和Last-Modify相同,服务器在响应头返回一个ETag字段,那么请求的时候就会在请求头中加入if-none-match

下面来看看代码,代码中我都会加入详细的注释:优化

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const crypto = require('crypto');

const server = http.createServer(function(req, res) {
  // 获取请求的资源名称
  let { pathname } = url.parse(req.url, true);
  // 获取文件路径
  let filepath = path.join(__dirname, pathname);

  /**
   * 判断文件是否存在
   */
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      let ifNoneMatch = req.headers['if-none-match'];
      let readStream = fs.createReadStream(filepath);
      let md5 = crypto.createHash('md5');

      // 经过流的方式读取文件而且经过md5进行加密,至关于转成一个hash字符串做为etag的值
      readStream.on('data', function(data) {
        md5.update(data);
      });
      readStream.on('end', function() {
        let etag = md5.digest('hex');
        // 验证etag,判断资源是否被修改过,若是没有则返回304
        if (ifNoneMatch === etag) {
          res.writeHead(304);
          res.end();
        } else {
          res.setHeader('Content-Type', mime.getType(filepath));
          // 第一次服务器返回的时候,会把文件的内容算出来一个标识,发给客户端
          fs.readFile(filepath, (err, content) => {
            // 客户端看到etag以后,也会把此标识保存在客户端,下次再访问服务器的时候,发给服务器
            res.setHeader('Etag', etag);
            fs.createReadStream(filepath).pipe(res);
          });
        }
      });
    }
  });
});
server.listen(8000, () => {
  console.log('listen to 8000 port');
});

强制缓存

图片描述

经过上图分析:ui

  • 强制缓存经过Cache-Control这个响应头中的max-age:60(缓存60s)来判断缓存是否过时
  • 若是过时了则从新向服务器请求资源
  • 若是没有过时,则不通过服务器,直接读取资源

强制缓存比较简单,直接看一下代码的实现加密

const http = require('http');
    const url = require('url');
    const path = require('path');
    const fs = require('fs');
    const mime = require('mime');
    
    const server = http.createServer(function(req, res) {
      let { pathname } = url.parse(req.url, true);
      let filepath = path.join(__dirname, pathname);
      fs.stat(filepath, (err, stat) => {
        if (err) {
          res.setHeader('Content-Type', mime.getType(filepath));
          // 设置缓存过时时间
          res.setHeader('Cache-Control', 'max-age=100');
          fs.createReadStream(filepath).pipe(res);
        } else {
          return send(req, res, filepath);
        }
      });
    });
    server.listen(8000, () => {
      console.log('listen to port 8000');
    });

强制缓存就是向浏览器设置一个过时时间例如cache-control:max-age=60表示这是一个60秒的过时时间,60秒之内浏览器都会从缓存读取该资源,超过60秒则访问服务器

cache-control还有另外几个值能够设置

  • private 客户端能够缓存
  • public 客户端和代理服务器均可以缓存
  • max-age=60 缓存内容将在60秒后失效
  • no-cache 须要使用对比缓存验证数据,强制向源服务器再次验证
  • no-store 全部内容都不会缓存,强制缓存和对比缓存都不会触发

总结

理解缓存对前端开发来讲十分的重要,这也是为什么把这篇文章写出来的缘由,后续会继续为你们带来node相关的文章,若是写错或很差的地方但愿你们指出来,若是以为写的还行,麻烦点个赞哈!

如下个人新我的微信公众号,也会为你们持续提供原创文章,欢迎你们关注,若是用户量足够,会在里面为你们提供一些项目类的视频教程,谢谢
图片描述

相关文章
相关标签/搜索