Merge pull request #11428 from alefranz/responsecaching-304-headers

ResponseCaching: include required headers in 304
This commit is contained in:
Stephen Halter 2019-07-02 18:23:14 -07:00 committed by GitHub
commit 3c0fb1a3f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 6 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -19,6 +19,10 @@ namespace Microsoft.AspNetCore.ResponseCaching
{
private static readonly TimeSpan DefaultExpirationTimeSpan = TimeSpan.FromSeconds(10);
// see https://tools.ietf.org/html/rfc7232#section-4.1
private static readonly string[] HeadersToIncludeIn304 =
new[] { "Cache-Control", "Content-Location", "Date", "ETag", "Expires", "Vary" };
private readonly RequestDelegate _next;
private readonly ResponseCachingOptions _options;
private readonly ILogger _logger;
@ -156,6 +160,17 @@ namespace Microsoft.AspNetCore.ResponseCaching
{
_logger.NotModifiedServed();
context.HttpContext.Response.StatusCode = StatusCodes.Status304NotModified;
if (context.CachedResponseHeaders != null)
{
foreach (var key in HeadersToIncludeIn304)
{
if (context.CachedResponseHeaders.TryGetValue(key, out var values))
{
context.HttpContext.Response.Headers[key] = values;
}
}
}
}
else
{

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -592,19 +592,25 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async Task Serves304_IfIfModifiedSince_Satisfied()
{
var builders = TestUtils.CreateBuildersWithResponseCaching();
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context =>
{
context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\"");
context.Response.Headers[HeaderNames.ContentLocation] = "/";
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
});
foreach (var builder in builders)
{
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
var initialResponse = await client.GetAsync("?Expires=90");
client.DefaultRequestHeaders.IfModifiedSince = DateTimeOffset.MaxValue;
var subsequentResponse = await client.GetAsync("");
initialResponse.EnsureSuccessStatusCode();
Assert.Equal(System.Net.HttpStatusCode.NotModified, subsequentResponse.StatusCode);
Assert304Headers(initialResponse, subsequentResponse);
}
}
}
@ -631,19 +637,25 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async Task Serves304_IfIfNoneMatch_Satisfied()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\""));
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context =>
{
context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\"");
context.Response.Headers[HeaderNames.ContentLocation] = "/";
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
});
foreach (var builder in builders)
{
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
var initialResponse = await client.GetAsync("?Expires=90");
client.DefaultRequestHeaders.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue("\"E1\""));
var subsequentResponse = await client.GetAsync("");
initialResponse.EnsureSuccessStatusCode();
Assert.Equal(System.Net.HttpStatusCode.NotModified, subsequentResponse.StatusCode);
Assert304Headers(initialResponse, subsequentResponse);
}
}
}
@ -814,6 +826,22 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
}
}
private static void Assert304Headers(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
{
// https://tools.ietf.org/html/rfc7232#section-4.1
// The server generating a 304 response MUST generate any of the
// following header fields that would have been sent in a 200 (OK)
// response to the same request: Cache-Control, Content-Location, Date,
// ETag, Expires, and Vary.
Assert.Equal(initialResponse.Headers.CacheControl, subsequentResponse.Headers.CacheControl);
Assert.Equal(initialResponse.Content.Headers.ContentLocation, subsequentResponse.Content.Headers.ContentLocation);
Assert.Equal(initialResponse.Headers.Date, subsequentResponse.Headers.Date);
Assert.Equal(initialResponse.Headers.ETag, subsequentResponse.Headers.ETag);
Assert.Equal(initialResponse.Content.Headers.Expires, subsequentResponse.Content.Headers.Expires);
Assert.Equal(initialResponse.Headers.Vary, subsequentResponse.Headers.Vary);
}
private static async Task AssertCachedResponseAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
{
initialResponse.EnsureSuccessStatusCode();