我们利用ASP.NET开发的大部分API都是为了对外提供资源,对于不易变化的资源内容,针对某个维度对其实施缓存可以很好地提供应用的性能。《内存缓存与分布式缓存的使用》介绍的两种缓存框架(本地内存缓存和分布式缓存)为我们提供了简单易用的缓存读写编程模式,本篇介绍的则是针对针对HTTP响应内容实施缓存,ResponseCachingMiddleware中间件赋予我们的能力(本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)。

目录
[S2201]基于路径的响应缓存(源代码

[S2202]基于指定的查询字符串缓存响应(源代码

[S2203]基于指定的请求报头缓存响应(源代码

[S2204]缓存屏蔽(源代码

[S2201]基于路径的响应缓存

为了确定响应内容是否被缓存,如下的演示程序针对路径“/{foobar?}”注册的中间件会返回当前的时间。如代码片段所示,我们调用UseResponseCaching扩展方法对ResponseCachingMiddleware中间件进行了注册, AddResponseCaching扩展方法则注册了该中间件依赖的服务。

using Microsoft.Net.Http.Headers;

var app = WebApplication.Create();
app.UseResponseCaching();
app.MapGet("/{foobar}", Process);
app.Run(); static DateTimeOffset Process(HttpResponse response)
{
response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(3600)
};
return DateTimeOffset.Now;
}

终结点处理方法Process在返回当前时间之前添加了一个Cache-Control响应报头,并且将它的值设置为“public, max-age=3600”(public表示缓存的是可以被所有用户共享的公共数据,而max-age则表示过期时限,单位为秒)。要证明整个响应的内容是否被缓存,只需要验证在缓存过期之前具有相同路径的多个请求对应的响应是否具有相同的主体内容。

GET http://localhost:5000/foo HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:13:39 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Content-Length: 35 "2021-12-14T10:13:39.8838806+08:00"
GET http://localhost:5000/foo HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:13:39 GMT
Server: Kestrel
Age: 3
Cache-Control: public, max-age=3600
Content-Length: 35 "2021-12-14T10:13:39.8838806+08:00"
GET http://localhost:5000/bar HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:13:49 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Content-Length: 35 "2021-12-14T10:13:49.0153031+08:00"
GET http://localhost:5000/bar HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:13:49 GMT
Server: Kestrel
Age: 2
Cache-Control: public, max-age=3600
Content-Length: 35 "2021-12-14T10:13:49.0153031+08:00"

如下所示的四组请求和响应是在不同时间发送的,其中两个和后两个请求采用的请求路径分别为“/foo”和“/bar”。可以看出采用相同路径的请求会得到相同的时间戳,意味着后续请求返回的内容来源于缓存,并且说明了响应内容默认是基于请求路径进行缓存的。由于请求发送的时间不同,所以返回的缓存副本的“年龄”(对应响应报头Age)也是不同的。

[S2202]基于指定的查询字符串缓存响应

一般来说,对于提供资源的API来说,请求的路径可以作为资源的标识,所以请求路径决定返回的资源,这也是响应基于路径进行缓存的理论依据。但是在很多情况下,请求路径仅仅是返回内容的决定性因素之一,即使路径能够唯一标识返回的资源,但是资源可以采用不同的语言来表达,也可以采用不同的编码方式,所以最终的响应的内容还是不一样的。在编写请求处理程序的时候,我们还经常根据请求携带的查询字符串来生成响应的内容。以我们的演示的返回当前时间戳的实例来说,我们可以利用请求携带的查询字符串“utc”或者请求报头“X-UTC”来决定返回的是本地时间还是UTC时间。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers; var app = WebApplication.Create();
app.UseResponseCaching();
app.MapGet("/{foobar?}", Process);
app.Run(); static DateTimeOffset Process(HttpResponse response,
[FromHeader(Name = "X-UTC")] string? utcHeader,
[FromQuery(Name ="utc")]string? utcQuery)
{
response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(3600)
}; return Parse(utcHeader) ?? Parse(utcQuery) ?? false
? DateTimeOffset.UtcNow : DateTimeOffset.Now; static bool? Parse(string? value)
=> value == null
? null
: string.Compare(value, "1", true) == 0 || string.Compare(value, "true", true) == 0;
}

由于响应缓存默认采用的Key是派生于请求的路径,但是对于我们修改过的这个程序来说,默认的这个缓存键的生成策略就有问题了。程序启动后,我们采用路径“/foobar”发送了如下两个请求,其中第一个请求返回了实时生成的本地时间(+08:00表示北京时间采用的时区),对于第二个情况下,我们本来希望指定“utc”查询字符串以返回一个UTC时间,但是我们得到却是缓存的本地时间。

GET http://localhost:5000/foobar HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:54:54 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Content-Length: 35 "2021-12-14T10:54:54.6845646+08:00"
GET http://localhost:5000/foobar?utc=true HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:54:54 GMT
Server: Kestrel
Age: 7
Cache-Control: public, max-age=3600
Content-Length: 35 "2021-12-14T10:54:54.6845646+08:00"

[S2203]基于指定的请求报头缓存响应

要解决这个问题,必须要让我们希望的缓存维度作为缓存键的组成部分。就我们演示程序来说,就是得让响应缓存的Key不仅仅包括请求的路径,还应该包括查询字符串“utc”和请求报头“X-UTC”的值。为此我们对演示的程序进行了相应的修改。如下面的代码片段所示,我们从当前HttpContext上下文中提取出IResponseCachingFeature特性,并将设置了它的VaryByQueryKeys属性使之包含了参与缓存的查询字符串的名称“utc”。为了让自定义请求报头“X-UTC”的值也参与缓存,我们将“X-UTC”作为Vary响应报头的值。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCaching;
using Microsoft.Net.Http.Headers; var app = WebApplication.Create();
app.UseResponseCaching();
app.MapGet("/{foobar?}", Process);
app.Run(); static DateTimeOffset Process(HttpContext httpContext,
[FromHeader(Name = "X-UTC")] string? utcHeader,
[FromQuery(Name ="utc")]string? utcQuery)
{
var response = httpContext.Response;
response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(3600)
}; var feature = httpContext.Features.Get<IResponseCachingFeature>()!;
feature.VaryByQueryKeys = new string[] { "utc" };
response.Headers.Vary = "X-UTC"; return Parse(utcHeader) ?? Parse(utcQuery) ?? false ? DateTimeOffset.UtcNow : DateTimeOffset.Now; static bool? Parse(string? value)
=> value == null? null: string.Compare(value, "1", true) == 0 || string.Compare(value, "true", true) == 0;
}

对于我们修正过演示程序来说,请求查询字符串“utc”的值会作为响应缓存键的一部分,我们在重启应用后发送了如下针对“/foobar”的四个请求。前两个请求和后两个请求采用相同的查询字符串(“?utc=true”和“?utc=false”),所以后一个请求会返回缓存的内容。

GET http://localhost:5000/foobar?utc=true HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:59:23 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T02:59:23.0540999+00:00"
GET http://localhost:5000/foobar?utc=true HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 02:59:23 GMT
Server: Kestrel
Age: 3
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T02:59:23.0540999+00:00"

从上面给出的报文的内容可以看出,响应报文具有一个值为“X-UTC”的Vary报头,它告诉客户端响应的内容会根据这个名为“X-UTC”的请求报头进行缓存。为了验证这一点,我们在重启应用后针对“/foobar”发送了如下四个请求,前两个请求和后两个请求采用相同的X-UTC(“X-UTC: True”和“X-UTC: False”),所以后一个请求会返回缓存的内容。

GET http://localhost:5000/foobar HTTP/1.1
X-UTC: True
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:05:06 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 34 "2021-12-14T03:05:06.977078+00:00"
GET http://localhost:5000/foobar HTTP/1.1
X-UTC: True
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:05:06 GMT
Server: Kestrel
Age: 3
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 34 "2021-12-14T03:05:06.977078+00:00"
GET http://localhost:5000/foobar HTTP/1.1
X-UTC: False
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:05:17 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T11:05:17.0068036+08:00"
GET http://localhost:5000/foobar HTTP/1.1
X-UTC: False
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:05:17 GMT
Server: Kestrel
Age: 19
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T11:05:17.0068036+08:00"

[S2204]缓存屏蔽

响应缓存通过复用已经生成的响应内容来提升性能,但不意味任何请求都适合以缓存的内容予以回复,请求携带的一些报头会屏蔽掉响应缓存。或者更加准确的说法是,客户端请求携带的一些报头会“提醒”服务端当前场景需要返回实时内容。比如携带Authorization报头的请求默认情况下将不会使用缓存的内容予以回复,下面的请求/响应体现了这一点。

GET http://localhost:5000/foobar HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:13:10 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T11:13:10.4605924+08:00"
GET http://localhost:5000/foobar HTTP/1.1
Authorization: foobar
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:13:17 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T11:13:18.0918033+08:00"

关于Authorization请求报头与缓存的关系,它与前面介绍的根据指定的请求报头对响应内容进行缓存是不一样的,当ResponseCachingMiddleware中间件在处理请求时,只要请求携带了此报头,缓存策略将不再使用。如果客户端对数据的实时性要求很高,那么它更希望服务总是返回实时生成的内容,这种情况下它利用利用携带的一些请求报头向服务端传达这样的意图,此时一般会使用到报头“Cache-Control:no-cache”或者“Pragma:no-cache”。这两个请求报头对响应缓存的屏蔽作用体现在如下所示的四组请求/响应中。

GET http://localhost:5000/foobar HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:15:16 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 34 "2021-12-14T11:15:16.423496+08:00"
GET http://localhost:5000/foobar HTTP/1.1
Cache-Control: no-cache
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:15:26 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T11:15:26.7701298+08:00"
GET http://localhost:5000/foobar HTTP/1.1
Pragma: no-cache
Host: localhost:5000 HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Dec 2021 03:15:36 GMT
Server: Kestrel
Cache-Control: public, max-age=3600
Vary: X-UTC
Content-Length: 35 "2021-12-14T11:15:36.5283536+08:00"

ASP.NET Core 6框架揭秘实例演示[34]:缓存整个响应内容的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  2. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  3. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  4. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  5. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  6. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  7. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  8. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  9. ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出

    针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...

随机推荐

  1. vue海康视频播放组件

    海康视频插件web文档 渲染组件后,调用initPlugin函数,传入一个code数组 <template> <div :title="name" :id=&qu ...

  2. C#实现[移除文件名中的非中文字符]

    更新记录: 2022年5月28日 从程序中抽出方法复用. 处理财务文件时写的一个小函数.用于移除文件名中的非中文字符. /// <summary> /// 移除文件名中的非中文字符 /// ...

  3. 送分题,ArrayList 的扩容机制了解吗?

    1. ArrayList 了解过吗?它是啥?有啥用? 众所周知,Java 集合框架拥有两大接口 Collection 和 Map,其中,Collection 麾下三生子 List.Set 和 Queu ...

  4. java常见的面试题(一)

    1.Collection 和 Collections 有什么区别? Collection 是一个集合接口(集合类的一个顶级接口).它提供了对集合对象进行基本操作的通用接口方法.Collection接口 ...

  5. 校验日期格式为yyyy-MM-dd

    /** * 校验时间 * * @param text * @return */ public static boolean checkTime(String text) { DateFormat fo ...

  6. Contest

    Contest 题目 链接 题目描述 \(n\) 支队伍一共参加了三场比赛. 一支队伍 \(x\) 认为自己比另一支队伍 \(y\) 强当且仅当 \(x\) 在至少一场比赛中比 \(y\) 的排名高. ...

  7. 广西省行政村边界shp数据/广西省乡镇边界/广西省土地利用分类数据/广西省气象数据/降雨量分布数据/太阳辐射数据

    ​  数据下载链接:数据下载链接 广西壮族自治区,地处中国南部,北回归线横贯中部,属亚热带季风气候区.南北以贺州--东兰一线为界,此界以北属中亚热带季风气候区,以南属南亚热带季风气候区. 数据范围:全 ...

  8. 一文解决Vue中实现 Excel下载到本地以及上传Excel

    相信大家在项目中经常会遇到一些上传下载文件的相关功能,本文就Excel的相关功能进行简述: 咱直接看代码: <div class="import-main-content"& ...

  9. 比起网易有数BI,也许这款数据可视化软件更适合你!

    有数BI是网易推出的面向企业客户的可视化敏捷BI产品.拥有数据填报和自助式商业智能分析产品,提供网页端和手机端应用,帮助客户快速实现数据填报.多维分析.大数据探索.实时大数据展示和成员分享. 山海鲸可 ...

  10. 对比学习下的跨模态语义对齐是最优的吗?---自适应稀疏化注意力对齐机制 IEEE Trans. MultiMedia

    论文介绍:Unified Adaptive Relevance Distinguishable Attention Network for Image-Text Matching (统一的自适应相关性 ...