Skip to content

Commit 628da23

Browse files
alefranzJohn Luo
authored and
John Luo
committed
Response Caching: Cache Head with Content-Length (dotnet#12652)
* Response Caching: Cache Head with Content-Length
1 parent 5b1b8bc commit 628da23

File tree

4 files changed

+65
-11
lines changed

4 files changed

+65
-11
lines changed

src/Middleware/ResponseCaching/src/ResponseCachingMiddleware.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.Threading.Tasks;
77
using Microsoft.AspNetCore.Http;
8-
using Microsoft.AspNetCore.Http.Features;
98
using Microsoft.Extensions.Caching.Memory;
109
using Microsoft.Extensions.Logging;
1110
using Microsoft.Extensions.ObjectPool;
@@ -345,7 +344,9 @@ internal void FinalizeCacheBody(ResponseCachingContext context)
345344
{
346345
var contentLength = context.HttpContext.Response.ContentLength;
347346
var bufferStream = context.ResponseCachingStream.GetBufferStream();
348-
if (!contentLength.HasValue || contentLength == bufferStream.Length)
347+
if (!contentLength.HasValue || contentLength == bufferStream.Length
348+
|| (bufferStream.Length == 0
349+
&& string.Equals(context.HttpContext.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase)))
349350
{
350351
var response = context.HttpContext.Response;
351352
// Add a content-length if required

src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -723,8 +723,10 @@ public async Task FinalizeCacheBody_Cache_IfContentLengthMatches()
723723
LoggedMessage.ResponseCached);
724724
}
725725

726-
[Fact]
727-
public async Task FinalizeCacheBody_DoNotCache_IfContentLengthMismatches()
726+
[Theory]
727+
[InlineData("GET")]
728+
[InlineData("HEAD")]
729+
public async Task FinalizeCacheBody_DoNotCache_IfContentLengthMismatches(string method)
728730
{
729731
var cache = new TestResponseCache();
730732
var sink = new TestSink();
@@ -734,6 +736,7 @@ public async Task FinalizeCacheBody_DoNotCache_IfContentLengthMismatches()
734736
context.ShouldCacheResponse = true;
735737
middleware.ShimResponseStream(context);
736738
context.HttpContext.Response.ContentLength = 9;
739+
context.HttpContext.Request.Method = method;
737740

738741
await context.HttpContext.Response.WriteAsync(new string('0', 10));
739742

@@ -749,6 +752,39 @@ public async Task FinalizeCacheBody_DoNotCache_IfContentLengthMismatches()
749752
LoggedMessage.ResponseContentLengthMismatchNotCached);
750753
}
751754

755+
[Theory]
756+
[InlineData(false)]
757+
[InlineData(true)]
758+
public async Task FinalizeCacheBody_RequestHead_Cache_IfContentLengthPresent_AndBodyAbsentOrOfSameLength(bool includeBody)
759+
{
760+
var cache = new TestResponseCache();
761+
var sink = new TestSink();
762+
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
763+
var context = TestUtils.CreateTestContext();
764+
765+
context.ShouldCacheResponse = true;
766+
middleware.ShimResponseStream(context);
767+
context.HttpContext.Response.ContentLength = 10;
768+
context.HttpContext.Request.Method = "HEAD";
769+
770+
if (includeBody)
771+
{
772+
// A response to HEAD should not include a body, but it may be present
773+
await context.HttpContext.Response.WriteAsync(new string('0', 10));
774+
}
775+
776+
context.CachedResponse = new CachedResponse();
777+
context.BaseKey = "BaseKey";
778+
context.CachedResponseValidFor = TimeSpan.FromSeconds(10);
779+
780+
middleware.FinalizeCacheBody(context);
781+
782+
Assert.Equal(1, cache.SetCount);
783+
TestUtils.AssertLoggedMessages(
784+
sink.Writes,
785+
LoggedMessage.ResponseCached);
786+
}
787+
752788
[Fact]
753789
public async Task FinalizeCacheBody_Cache_IfContentLengthAbsent()
754790
{

src/Middleware/ResponseCaching/test/ResponseCachingTests.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.IO;
65
using System.Net.Http;
7-
using System.Threading;
86
using System.Threading.Tasks;
9-
using Microsoft.AspNetCore.Builder;
107
using Microsoft.AspNetCore.Http;
11-
using Microsoft.AspNetCore.Http.Features;
128
using Microsoft.AspNetCore.TestHost;
139
using Microsoft.Net.Http.Headers;
1410
using Xunit;
@@ -775,6 +771,24 @@ public async Task ServesCachedContent_IfCachedVaryByNotUpdated_OnCacheMiss()
775771
}
776772
}
777773

774+
[Fact]
775+
public async Task ServesCachedContent_IfAvailable_UsingHead_WithContentLength()
776+
{
777+
var builders = TestUtils.CreateBuildersWithResponseCaching();
778+
779+
foreach (var builder in builders)
780+
{
781+
using (var server = new TestServer(builder))
782+
{
783+
var client = server.CreateClient();
784+
var initialResponse = await client.SendAsync(TestUtils.CreateRequest("HEAD", "?contentLength=10"));
785+
var subsequentResponse = await client.SendAsync(TestUtils.CreateRequest("HEAD", "?contentLength=10"));
786+
787+
await AssertCachedResponseAsync(initialResponse, subsequentResponse);
788+
}
789+
}
790+
}
791+
778792
private static void Assert304Headers(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
779793
{
780794
// https://tools.ietf.org/html/rfc7232#section-4.1

src/Middleware/ResponseCaching/test/TestUtils.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7-
using System.IO.Pipelines;
87
using System.Linq;
98
using System.Net.Http;
109
using System.Text;
11-
using System.Threading;
1210
using System.Threading.Tasks;
1311
using Microsoft.AspNetCore.Builder;
1412
using Microsoft.AspNetCore.Hosting;
@@ -23,7 +21,6 @@
2321
using Microsoft.Extensions.Primitives;
2422
using Microsoft.Net.Http.Headers;
2523
using Xunit;
26-
using ISystemClock = Microsoft.AspNetCore.ResponseCaching.ISystemClock;
2724

2825
namespace Microsoft.AspNetCore.ResponseCaching.Tests
2926
{
@@ -61,6 +58,12 @@ private static bool TestRequestDelegate(HttpContext context, string guid)
6158
headers.Date = DateTimeOffset.UtcNow;
6259
headers.Headers["X-Value"] = guid;
6360

61+
var contentLength = context.Request.Query["ContentLength"];
62+
if (!string.IsNullOrEmpty(contentLength))
63+
{
64+
headers.ContentLength = long.Parse(contentLength);
65+
}
66+
6467
if (context.Request.Method != "HEAD")
6568
{
6669
return true;

0 commit comments

Comments
 (0)