最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Koa 中返回 html 文件引发的思考

    正文概述 掘金(B2D1)   2021-05-22   1379

    前言

    在使用 Koa 框架搭建网站时,一般返回 index.html 当做入口文件,这需要设置特定的 response header.

    由于 Koa 自身极简的设计思想,我相信没看过源码的同学,第一时间会写出如下代码:

    const Koa = require("koa");
    const fs = require("fs");
    
    const app = new Koa();
    
    app
      .use(ctx => {
        ctx.set("Content-Type", "text/html");
        ctx.body = fs.readFileSync("index.html");
      })
      .listen(3000);
    

    这当然可行,还能加强你对 HTTP Headers 的理解,但真正熟悉 Koa 的人,不屑于用如此原始的方法。

    别看 Koa 的源码短小精悍,它依旧为开发者提供了诸多便捷的功能,例如此文着重介绍的 ctx.type.

    带你认识 ctx.type

    先上代码,只需将上下文(ctx)的 type 设置为 "html" 即可:

    app
      .use(ctx => {
        ctx.type = "html";
        ctx.body = fs.readFileSync("index.html");
      })
      .listen(3000);
    

    http(没有安装这个命令的小伙伴,访问这个网址:httpie.io/ )在终端进行测试:

    Koa 中返回 html 文件引发的思考 Koa 中返回 html 文件引发的思考

    其实它的实现非常简单,通过传入不同的 type,自动设置对应的 Content-Type response header.

    具体源码戳着 lib/response.js.

    module.exports = {
      // ...
      set type(type) {
        type = getType(type);
        if (type) {
          this.set("Content-Type", type);
        } else {
          this.remove("Content-Type");
        }
      },
    };
    

    那么 getType 函数是个啥?原来借助了第三方包的“神秘力量”。

    const getType = require("cache-content-type");
    

    出现了关键词 "cache",作为一名专业的前端工程师(切图仔),察觉到这里一定做了某些优化。

    为了验证我的猜测,我查看了此文件在 GitHub 上的历史提交记录。

    果不其然,在 18 年的 7 月,一个有关性能优化的 PR 被合入 master.

    Koa 中返回 html 文件引发的思考

    来看看具体改动了哪些内容:

    Koa 中返回 html 文件引发的思考

    破案了!最先使用的是 mime-types 库。

    这个库实现同样很简单,大致就是从一个 JSON 文件(mime-db/db.json)匹配文件后缀名,返回 Content-Type,如 html -> text/html 的映射关系,库还会根据情况来为你加上 charset=utf-8.

    // db.json
    {
      "text/html": {
        "source": "iana",
        "compressible": true,
        "extensions": ["html", "htm", "shtml"]
      }
    }
    

    这里推荐一个非常 nice 的在线运行 Node.js package 的网站:npm.runkit.com/mime-types.

    你可以在浏览器上在线体验这些库、还包括查看源码,README.md 等功能。

    Koa 的优化之路:LRU

    让我们把视角转向现在,Koa 中 mime-types 库已被无情抛弃,取而代之的是 cache-content-type 库.

    它的源码更简单,总共就 15 行:

    "use strict";
    
    const mimeTypes = require("mime-types");
    const LRU = require("ylru");
    
    const typeLRUCache = new LRU(100);
    
    module.exports = type => {
      let mimeType = typeLRUCache.get(type);
      if (!mimeType) {
        mimeType = mimeTypes.contentType(type);
        typeLRUCache.set(type, mimeType);
      }
      return mimeType;
    };
    

    核心思路是维护一个 Cache:

    1. 当 Cache 中没有该 type 对应的 mimeType 时,会去进行一次查找并将结果 mimeType 存储在 Cache 中
    2. 当第二次查找 type,发现 Cache 中已存在,就直接返回对应的 mimeType,不再进行查找

    因为存储 MIME 信息的 JSON 文件非常大,每次查找都非常耗时,所以尽可能减少查找次数。

    细心的小伙伴发现了,这个 typeLRUCache 为什么还要设置最大容量为 100 个?难道存储超过 100 个 type 后,之后查找 type 的结果就不能被缓存了吗?

    No,因为代码中 typeLRUCache 跟我说的 Cache 不是同一个东西。

    我说的 Cache 是一种单纯以空间换时间的方法,但也不能无脑去拓宽空间,假设容量为 1000000 或 1e10 个,这会急剧增加程序的运行内存,引起电脑卡顿、死机。

    归功于 ylru 这个库,我们有一种折中的方案。

    因为它实现了 Least recently used (LRU) 算法,这个算法做了什么呢?我举个栗子,便于大家理解。

    一个小区里只有两个停车位 P1 和 P2,但有四个车主分别为 A、B、C、D.

    • 第一天特斯拉车主 A 使用了 P1

    • 第二天宝马车主 B 使用了 P2

    • 第三天保时捷车主 C 来停车,发现没有停车位了,咋办呢?

      • 小区物业出于人性化考虑,宝马车主 B 一天前刚用的 P2,而特斯拉车主 A 是两天前用的 P1,估计特斯拉车主 A 已经去世了,于是强制让 A 移出了停车位
      • 保时捷车主 C 顺利将车停入 P1
    • 第四天上午,宝马车主 B 离开了停车位

    • 第四天下午,宝马车主 B 进入了停车位

    • 第五天奔驰车主 D 来停车,发现停车位已满,咋办呢?

      • 小区物业出于人性化考虑,宝马车主 B 一天前刚频繁使用过 P2,而保时捷车主 C 已经一天没动静了,就算你是豪华车也没用,强制让保时捷车主 C 移出了停车位
      • 奔驰车主 D 顺利将车停入 P1

    总结:停车位总量不变,但至少能让最近经常使用停车位车主的权益得到保障,停车位始终有你一份,而不是看先来后到,也不是看谁的车贵。

    类比到 typeLRUCache 上,只能存储 100 个缓存结果,防止容量无限增大,但能保证最近被频繁查找 type 的结果始终被缓存,提示后续查找效率。

    我在这写了一个简易版 LRU 的 demo:

    class LRUCache {
      constructor(capacity) {
        this.cache = new Map();
        this.capacity = capacity;
      }
      get(key) {
        if (this.cache.has(key)) {
          const temp = this.cache.get(key);
          this.cache.delete(key);
          this.cache.set(key, temp);
          return temp;
        }
        return -1;
      }
      put(key, value) {
        if (this.cache.has(key)) {
          this.cache.delete(key);
        } else if (this.cache.size >= this.capacity) {
          this.cache.delete(this.cache.keys().next().value);
        }
        this.cache.set(key, value);
      }
    }
    
    const cache = new LRUCache(2);
    
    cache.put("A", 1);
    cache.put("B", 2);
    cache.get("A"); // 返回  1
    cache.put("C", 3); // 该操作会使得 'B' 作废
    cache.get("B"); // 返回 -1 (未找到)
    cache.put("D", 4); // 该操作会使得 'A' 作废
    cache.get("A"); // 返回 -1 (未找到)
    cache.get("C"); // 返回  3
    

    结尾

    这篇文章到这就 happy ending 了,从设置 Content-Type 出发,我们一连看了 4 个库的源码,这波血赚不亏。

    如果对 Koa 源码感兴趣的同学,请猛击 Koa 源码剖析 & 实现,教你手写一个 “复刻版” Koa.


    起源地下载网 » Koa 中返回 html 文件引发的思考

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元