ASP.NET Core 6框架揭秘实例演示[34]:缓存整个响应内容
我们利用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]:缓存整个响应内容的更多相关文章
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
- ASP.NET Core 6框架揭秘实例演示[09]:配置绑定
我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...
- ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式
依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...
- ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式
在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...
- ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法
一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...
- ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]
<诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...
- ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法
为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...
- ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出
针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...
随机推荐
- 攻防世界pwn题:实时数据检测
0x00:查看文件 一个32位的文件,canary.NX.PIE保护机制均关闭. 0x01:用IDA进行静态分析 程序很简单,输入一串字符(个数限制:512),然后再输出.最后根据key变量进行条件语 ...
- SAP PP- OPK8生产订单打印 配置Smart form.
OPK8 正常情况下是不可以配置Smart form 的 OPK8进入工单打印配置界面,选择Forms, 你会发现只有Script form 和PDF form(Adobe form)可选的,没有配置 ...
- 毫秒值的概念和作用 --Date类的构造方法和成员方法
一, Date类类 Date 表示特定的瞬间,精确到毫秒. 毫秒:千分之一秒作用:可以对时间和日期进行计算可一把日期转换为毫秒进行计算,计算完毕,再转换为日期. 把日期转换为毫秒:当前的日期:202 ...
- do-while循环和三种循环的区别
循环语句3--do...while do...while循环格式 初始化表达式① do{ 循环体③ 步进表达式④ }while(布尔表达式②); 执行流程 执行顺序:①③④>②③④>②③④ ...
- 用户数据交互 Scanner
# 用户交互 Scanner ``` java package study5ran2yl.study; import java.util.Scanner; public class demo09 { ...
- error: cannot open .git/FETCH_HEAD: Permission denied
可能原因:该操作的执行者对该目录没有写权限 解决:1.类Unix平台,使用chown将目录改为自己: 2.Windows平台,取消只读选项,给everyone用户所有权限:
- 01 Mybatis框架添加英雄步骤
客户端发出请求的几种方式 通过浏览器的地址栏中发出请求 通过html页面中的超链接发出请求 通过html页面中的form表单发出请求 通过前端框架发出请求 工程中使用数据库需要做的几件事: 在pom. ...
- NFS配置-实现多服务器共享目录
NFS网络文件系统 为什么要用NFS? 前端所有的应用服务器接收到用户上传的图片.文件.视频,都会统一放到后端的存储上.共享存储的好处:方便数据的查找与取出,缺点:存储服务器压力大,坏了丢失全部数据. ...
- typescript中的esModuleInterop选项
当没有加esModuleInterop时 库的代码: export const a = 1; export default function b() {} 生成代码 exports.__esModul ...
- DateFormat类的format方法和parse方法
/** * 使用DateFormat类中的方法format,把日期格式化为文本 * String format(Date date) 按照指定的模式把Date日期格式化为符合模式的字符串 * 使用步骤 ...