「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
本文继续阅读 koa 依赖的库,这节看下几个和 content 功能相关的库。
在 koa request 上导出的四个方法:accepts、acceptsEncodings、acceptsCharsets、acceptsLanguages,用来判断请求方是否接受某一种类型数据,可以不传参数,也可以传递 1 或多个参数,也可以使用数组,如果传入多个返回符合条件的第一个,其内部都是通过 accepts 库实现的。
accept 相关信息都位于 http request header 上,分别对应属性 accept、accept-encoding、accept-charset、accept-language,因此 accepts 接收 req 对象作为参数,在 accepts 内部依赖了 negotiator 库,header 信息的处理逻辑在 negotiator 库内部处理。
先看 accepts 中的逻辑,首先是参数处理,未传递参数时直接返回对应的 header 信息,对于有参数场景会统一转换为数组,这样传入 negotiator 时统一按照数组来处理。在 negotiator 内部是一系列格式转化逻辑,包括大小写转化、通配符处理等,最终会返回最优解数组。accepts 收到结果后返回数组第一项内容。
Accepts.prototype.types = function (types_) {
var types = types_ // support flattened arguments
if (types && !Array.isArray(types)) {
types = new Array(arguments.length)
for (var i = 0; i < types.length; i++) {
types[i] = arguments[i]
}
} // no types, return all requested types
if (!types || types.length === 0) {
return this.negotiator.mediaTypes()
} // no accept header, return first given type
if (!this.headers.accept) {
return types[0]
}
var mimes = types.map(extToMime)
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
var first = accepts[0]
return first ? types[mimes.indexOf(first)] : false
}
在 accepts 库中,accept 的处理与其他三项略有不同,在 http 请求中,accept type 有一个统一的 mime 类型系统,因此这里会做一个 mime 类型的标准化转换,如 json 标准写法应该是 text/json,这个过程是调用 mime-types 库的 lookup 函数实现的,其内部逻辑是匹配 mime-db 中的 extensions 字段。
因为 accept 和 content-type 是密切相关的,接下来我们看 content-type 的应用。在 http 请求和响应中都有 content-type header,在请求中用来标识请求携带的内容的格式,在响应中表示服务器返回的数据格式。一次请求流程:请求方通过 accept 告诉服务器自己可以接收的数据格式,服务器根据 accept 信息和自身能力返回 content-type 符合要求的数据。
content-type 是一个用来解析 header 中 content-type 信息的库,在 koa 中 request 的 charset 方法使用到了这个库的 parse 方法,用来获取 content-type 的 charset 字段信息。content-type 库源码很短,导出了 parse 和 format 两个方法,内部的核心逻辑是使用正则表达式进行字符串匹配,具体的代码在此不展开阅读了。
cache-content-type 也是 koa 依赖的一个和 content-type 处理有关的库,虽然名字看上去应该是给 content-type 库添加了缓存,但打开 readme 就会看到它其实是带缓存的 mime-types 库,前面在处理请求中的 accept 时用到了 mime-types,与之对应的,响应 header 中 content-type 的属性值也应该是 mime 类型,因此在 koa 中,response 下面 type 属性的 setter 方法中使用了 cache-content-type 库来查询 mime 类型并为 response header 的 Content-Type 属性设值。
'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-content-type 的全部源代码,内容很少,就是在调用 mime-types 库的基础上添加了缓存,这里的缓存逻辑在 ylru 库中,最大缓存数为 100,基于 LRU 算法实现过期淘汰。LRU(Least Recently Used)是最近最少使用的意思,是一种很常用的缓存淘汰策略,在操作系统的页面置换算法中有使用到。对算法感兴趣可以阅读 ylru 源码。
由于本系列是 koa 依赖的源码阅读,以直接依赖库为主,间接依赖的库实现细节不在此展开。