Middleware (5) — ResponseCaching

Feng Gao
5 min readSep 14, 2021

1. Overview

我们都知道缓存 ( cache ) 是提高性能的法宝。内存,数据库,网络中缓存机会无处不在,在 Web 应用程序同样也是如此。比如浏览器在打开一个网页的时候,如果需要加载图片,CSS 或者 JavaScript 资源的时候,如果之前浏览器下载过这些资源,就可以跳过 HTTP 请求的部分,这是浏览器私有的缓存。

还有一些缓存是在服务端实现的,将请求的响应缓存起来,在后续的请求如果命中缓存,则直接返回。

这是一个响应缓存的示意图

这里的 Cache 可以指代一个缓存中间件。

2. ResponseChachingOptions

ResponseCahcingOptions 的定义如下

  • SizeLimit 定义了所有的响应缓存的大小,默认 100 兆
  • MaximumBodySize 单个响应缓存大小,默认 64 兆
  • UseCacheSenesitivePaths 是否请求的路径是大小写敏感,默认不敏感

3. IResponseCahingPolicyProvider

这个接口定义了缓存策略的操作,比如是否允许查询缓存,是否允许保存缓存,缓存的是否有效。

3.1 AttemptResponseCaching

从中我们可以知道,是否允许缓存的条件是

  • 只有 GetHead 请求才允许缓存查询
  • 如果 Header 中包含了 Authorization 字段,则不允许缓存查询

3.2 AllowCacheLookup

这里同样也定了另外的缓存查询条件

  • 如果请求的 Header 中包含了 Cache-Control: No-cache 条件,则跳过缓存查询
  • 如果请求中 Header 中 抱恨了 Pragma:No-Cache 条件,则跳过缓存查询(注意这是 HTTP/1.0 的协议)

3.3 AllowCacheStorage

如果请求的 Header 中包含 Cache-Control: no-store 则返回的请求不要进行缓存。

3.4 IsResponseCacheable

这里定了若干响应能够被缓存的条件

  • ResponseHeader 只能包含 cache-control: public 字段,在 HTTP 协议中, Public 意味着响应可以被任何缓存保存;与之相对的是 private ,它表明响应只针对单个用户,缓存起来没有很大用处。
  • ResponseHeader 包含了 cache-control:no-store 字段,表示不能缓存
  • ResponseHeader 包含了 cache-control:no-cache 字段,表示不能缓存
  • ResponseHeader 包含了 Set-Cookie 字段,不进行缓存
  • ResponseHeader 包含了 Vary: * 则不进行缓存处理。 Vary 字段表明在将来的请求的时候,哪些请求将来可以使用缓存,比如如下

一开始我们的请求是接受 Content-Encoding: gzip 的请求,在请求返回的response 中包含了 Var:Content-Encoding 这个字段,那么 Cache 的时候也将 br 这个字段也放入到缓存中。

  • ResponseHeader 包含了 cache-control:private , 则不能进行缓存
  • ResponseStatusCode 不是 200, 则不进行缓存操作
  • ResponseResponseTime 大于 ResponseExpires ,则不进行缓存操作
  • ResponseAge 大于 maxAge ,则不进行缓存操作
  • 否则都可以进行缓存

3.5 IsCachedEntryFresh

主要是用来判断返回的请求,是否满足 Fresh 的要求。

4 IResponseCache

IResponseCache 主要缓存的 Response 的读取和写入操作, 操作的对象是 IResponseCacheEntry 对象

主要缓存的对象是响应的上面的字段。

5. IResponseCachingKeyProvider

IResponseCache 负责将缓存的响应放入到缓存中,那么缓存的 Value 我们刚刚讨论过了,那么缓存的 Key 该如何确定呢? IResponseChacingKeyProvider 负责根据请求创建 key , ResponseCahingKeyProvider 是其中的一个实现,那么它是怎么做到的呢?

BaseKey 的创建的格式如下

GET\x1eSchema\x1eHOST:PORT/PathBase/PATH

StorageVaryKey 的格式如下

BaseKey\xleH\xleHeaderName=HeaderValue\xleQ\xleQueryName=QueryValue1\x1fQueryValue2

LookupVaryKey

[ StroageVaryKey ]

6. Invoke Process

整个过程比较 straightforward

  1. 构造一个 ResponseCachingContext 对象,该对象在封装了 HttpContext ,并且增加了一个属性
  2. 然后是通过 PolicyProviderAttemptResponseCaching 来判断是否可以通过缓存来操作
  3. 如果是,则返回判断是否允许缓存查找,并且尝试从缓存中获取,如果成功,则理解返回
  4. 如果没有成功,则判断是否允许缓存操作
  5. 如果是,调用 ShimResponseStream 方法,来做一些预处理
  6. 调用 next 方法,继续后续的操作
  7. 调用 FinalizeCacheBody 方法,将后续 middleware 的得到的 response 缓存起来
  8. 如果上述的都不满足条件,则直接调用 _next 的中间件。

--

--

Feng Gao

A software developer in Microsoft at Suzhou. Most articles spoken language is Chinese. I will try with English when I’m ready