using Microsoft.Net.Http.Headers; var app = WebApplication.Create(); app.UseResponseCaching(); app.MapGet("/{foobar}", Process); app.Run(); static DateTimeOffset Process(HttpResponse response) { // 堆代码 duidaima.com 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)也是不同的。
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"
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"响应缓存通过复用已经生成的响应内容来提升性能,但不意味任何请求都适合以缓存的内容予以回复,请求携带的一些报头会屏蔽掉响应缓存。或者更加准确的说法是,客户端请求携带的一些报头会“提醒”服务端当前场景需要返回实时内容。比如携带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"