这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」
本文为 koa 依赖系列文章,前几篇也可以在站内查看:
- koa 中依赖的库 parseurl
- Koa 依赖的库 type-is 和 content-disposition
- Koa 依赖的库 accepts、content-type 和 cache-content-type
- Koa 依赖的库 encodeurl 和 escape-html
- Koa 中依赖的库 statuses
- Koa 依赖的库 cookies
- Koa 依赖的库 on-finished 和 destroy
- Koa 依赖的库 http-errors 和 http-assert
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 协议相关的几个依赖库的源码阅读到此就结束了,后面文章会再看剩下几个小的工具库依赖。