「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战」
本文继续 koa 依赖的库源码阅读,上一篇看了 content-type 相关的 accepts、content-type 和 cache-content-type,这一篇继续看一些和 content 相关的库。
type-is 也是一个和 content-type 有关的库,koa 中 request 和 response 下面的 is 方法是调用 type-is 库实现的,这个库做的事情是判断当前请求或响应的 content-type 是否属于某种类型。
先看导出部分:
module.exports = typeofrequest
module.exports.is = typeis
module.exports.hasBody = hasbody
module.exports.normalize = normalize
module.exports.match = mimeMatch
默认导出的是 typeofrequest,此方法支持直接传入 req 作为参数,如果是非 req 场景,可以使用 is 导出,此方法接收 content-type 作为参数。在 koa request 中使用的是默认导出,在 response 中使用的是 is 导出。后面的参数可以传入多个或一个,也可以使用数组,这里又是一个典型的参数处理逻辑:
var types = types_
if (arguments.length > 2) {
types = new Array(arguments.length - 1)
for (var i = 0; i < types.length; i++) {
types[i] = arguments[i + 1]
}
}
typeofrequest 中首先取从 header 中取出 content-type 信息:req.headers['content-type'] 之后也会传入 typeis 中,实际上核心逻辑都在 typeis 中。
在 typeis 中,首先会进行一个 normalizeType 的过程:
function normalizeType (value) {
// parse the type
var type = typer.parse(value)
// remove the parameters
type.parameters = undefined
// reformat it
return typer.format(type)
}
此处的 typer 是使用的 media-typer 库,移除了 parameters。
传入的类型信息也会经过 normalize 处理,这一步还是调用 mime-types 库的 lookup 方法:
function normalize (type) {
if (typeof type !== 'string') {
// invalid type
return false
}
switch (type) {
case 'urlencoded':
return 'application/x-www-form-urlencoded'
case 'multipart':
return 'multipart/*'
}
if (type[0] === '+') {
// "+json" -> "*/*+json" expando
return '*/*' + type
}
return type.indexOf('/') === -1
? mime.lookup(type)
: type
}
最后是遍历匹配的逻辑:
function mimeMatch (expected, actual) {
// invalid type
if (expected === false) {
return false
}
// split types
var actualParts = actual.split('/')
var expectedParts = expected.split('/')
// invalid format
if (actualParts.length !== 2 || expectedParts.length !== 2) {
return false
}
// validate type
if (expectedParts[0] !== '*' && expectedParts[0] !== actualParts[0]) {
return false
}
// validate suffix wildcard
if (expectedParts[1].substr(0, 2) === '*+') {
return expectedParts[1].length <= actualParts[1].length + 1 &&
expectedParts[1].substr(1) === actualParts[1].substr(1 - expectedParts[1].length)
}
// validate subtype
if (expectedParts[1] !== '*' && expectedParts[1] !== actualParts[1]) {
return false
}
return true
}
此外,hasBody 也很简单,判断 transfer-encoding 和 content-length 两个 header 即可:
function hasbody (req) {
return req.headers['transfer-encoding'] !== undefined ||
!isNaN(req.headers['content-length'])
}
以上是 type-is 库的全部逻辑,可以看到涉及 content-type 相关的功能都离不开 mime 相关的判断,因此它们都依赖了 mime-types 解析库。
在 http 响应中,除了 content-type,还有一个和内容相关的字段 content-disposition,content-disposition 用来处理文件下载的场景。不设置 content-disposition 或者值为 inline 时,文件是预览模式,此时就是在网页中查看资源。把 content-disposition 设置为 attachment 浏览器会认为这是一个需要下载的文件,此时将会调起浏览器下载功能下载内容资源。attachment 后面可以设置 filename 指定下载文件名。
在 koa response 中提供了 attachment 方法,可以为 res 设置 content-disposition header。在 attachment 方法内部使用了 content-disposition 库进行 header 内容的组装。content-disposition 也提供了默认的组装方法和解析的 parse 方法,与其他 header 解析库类似,内部主要是字符串匹配逻辑,涉及到一系列正则表达式在此不做展开。