资讯专栏INFORMATION COLUMN

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

Stardustsky / 946人阅读

摘要:浏览器缓存的使用是提高用户体验的一个重要途径,通常也是优化前端的一种重要方式。浏览器看到就会去读取缓存信息并呈现。

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

浏览器缓存的使用是提高用户体验的一个重要途径,通常也是优化前端的一种重要方式。利用好了缓存可以加快页面的浏览,降低服务器的压力,减少网络损耗等功能。

浏览器缓存分类

协商缓存

强制缓存

协商缓存

通过上图分析:

客户端向服务器请求资源

验证标识,如果标识通过了验证,则会响应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");
});
强制缓存

通过上图分析:

强制缓存通过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相关的文章,如果写错或不好的地方希望大家指出来,如果觉得写的还行,麻烦点个赞哈!

以下我的新个人微信公众号,也会为大家持续提供原创文章,欢迎大家关注,如果用户量足够,会在里面为大家提供一些项目类的视频教程,谢谢

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108931.html

相关文章

  • 深入浅出node.js总结-模块机制(1)

    摘要:先天就缺乏一项功能模块通过标签引入代码的方式显得杂乱无章,语言自身毫无组织和约束能力。与文件模块区别地方在于它从内存中加载缓存执行结果的位置核心模块在对象上,文件模块在对象上未完待续 javascript先天就缺乏一项功能:模块 javasciprt 通过script标签引入代码的方式显得杂乱无章,语言自身毫无组织和约束能力。人们不得不用命名空间等方式人为地约束代码,以求达到安全和易用的...

    jifei 评论0 收藏0
  • 前端基础

    摘要:谈起闭包,它可是两个核心技术之一异步基于打造前端持续集成开发环境本文将以一个标准的项目为例,完全抛弃传统的前端项目开发部署方式,基于容器技术打造一个精简的前端持续集成的开发环境。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老鸟,不论是面试求职,还是日...

    graf 评论0 收藏0
  • node核心特性理解

    摘要:概述本文主要介绍了我对的一些核心特性的理解,包括架构特点机制核心模块与简单应用。在此期间,主线程继续执行其他任务。延续了浏览器端单线程,只用一个主线程执行,不断循环遍历事件队列,执行事件。 原文地址在我的博客,转载请注明来源,谢谢! node是在前端领域经常看到的词。node对于前端的重要性已经不言而喻,掌握node也是作为合格的前端工程师一项基本功了。知道node、知道后端的一些东西...

    huangjinnan 评论0 收藏0
  • Node.js入门:模块机制

    摘要:模块载入策略的模块分为两类,一类为原生核心模块,一类为文件模块。最后传入对象的,方法,,文件名,目录名作为实参并执行。在这个主文件中,可以通过方法去引入其余的模块。以上所描述的模块载入机制均定义在中。 CommonJS规范  早在Netscape诞生不久后,JavaScript就一直在探索本地编程的路,Rhino是其代表产物。无奈那时服务端JavaScript走的路均是参考众多服务器端...

    alanoddsoff 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<