Koa 依赖的库 fresh 和 vary

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

本文为 koa 依赖系列文章,前几篇也可以在站内查看:

fresh 是一个判断 http 缓存是否生效的库,在 koa 的 request 上有 fresh 和 stale 两个属性 getter,分别表示缓存有效还是无效,相关的能力是使用 fresh 库实现的。

先复习一下浏览器的缓存知识:浏览器缓存分为强缓存和协商缓存两种:

  • 强缓存依靠 expires 和 cache-control 两个 header,里面记录缓存过期情况。当缓存有效时客户端直接加载本地缓存资源,返回 200 from memory cache 或 from disk cache,强缓存场景不需要向服务器发请求。
  • 协商缓存需要发起协商请求询问服务器,在 request 中携带 if-modified-since 和 if-none-match 两种 header 来向服务器询问缓存过期情况,从 response 中可以获取到 last-modified 和 etag 信息,这里根据时间和内容是否发生变化来确定缓存是否依旧有效,缓存有效直接返回 304 Not Modified,客户端收到 304 后加载本地资源。

现在来看 fresh 的源码,默认导出 fresh 函数接收 requestHeader 和 responseHeader 作为参数,首先检查是否有 if-modified-since 或 if-none-match header。这里不需要两个同时存在,实际上二者有一个就可以判断出缓存是否有效,其中 if-modified-since 是基于时间判断,if-none-match 是基于类型判断,由于时间只能精确到秒,因此 if-modified-since 不够准确,因此如果有 if-none-match 信息会优先进行 etag 判断:

// if-none-match
if (noneMatch && noneMatch !== '*') {
	var etag = resHeaders['etag']

	if (!etag) {
		return false
	}

	var etagStale = true
	var matches = parseTokenList(noneMatch)
	for (var i = 0; i < matches.length; i++) {
		var match = matches[i]
		if (match === etag || match === 'W/' + etag || 'W/' + match === etag) {
			etagStale = false
			break
		}
	}

	if (etagStale) {
		return false
	}
}
复制代码

之后是基于时间的判断:

// if-modified-since
if (modifiedSince) {
	var lastModified = resHeaders['last-modified']
	var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))

	if (modifiedStale) {
		return false
	}
}
复制代码

这里的逻辑还是比较明确的,有一种特殊情况,如果设置了 cache-control 为 no-cache 这里会直接返回 false

if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
	return false
}
复制代码

理解了浏览器的缓存流程,fresh 的源码还是比较清晰的,接下来再看一个与缓存有密切关系的 header — vary。

vary 是用来设置 vary header 的一个库,因此在阅读源码之前先要了解一下 vary 是什么。

Vary 的定义是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该使用一个缓存作为响应还是向源服务器请求一个新的响应,看到这里我们可以知道 Vary 与缓存策略有关,我们来看一个具体的例子:

有两个请求除了 User-Agent 之外完全相同,后一次请求是否可以使用缓存呢?

按照常规逻辑肯定是可以的,但是如果服务器为不同的 User-Agent 返回了不同内容呢?此时是不应该进入缓存逻辑的,这种情况我们就需要设置 Vary: User-Agent,告知客户端缓存内容会因为 User-Agent 的改变发生变化。

上面是对 Vary header 的一个简单理解,回到 koa 中,它提供了用来设置 vary header 的 vary 方法,方法内部直接调用的 vary 库:

vary (field) {
	if (this.headerSent) return

	vary(this.res, field)
}
复制代码

前面我们已经知道 vary 的值是多个 header 名的字符串,因此在 vary 内部和常见的字符串 header 的处理类似,都是一些匹配解析格式化拼接的过程,这里详细代码不再展开了。

至此 koa 中和 http 协议相关的几个依赖库的源码阅读到此就结束了,后面文章会再看剩下几个小的工具库依赖。

おすすめ

転載: juejin.im/post/7032725763397222414