Koa 依赖的库 type-is 和 content-disposition

1,043 阅读2分钟

「这是我参与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-encodingcontent-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 解析库类似,内部主要是字符串匹配逻辑,涉及到一系列正则表达式在此不做展开。