ResponseCaching: include required headers in 304

This commit is contained in:
Alessio Franceschelli 2019-06-20 23:10:59 +01:00
parent 1521e6fa72
commit be7fa3b9fd
2 changed files with 50 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;
@ -20,6 +20,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;
@ -157,6 +161,18 @@ namespace Microsoft.AspNetCore.ResponseCaching
{
_logger.NotModifiedServed();
context.HttpContext.Response.StatusCode = StatusCodes.Status304NotModified;
if (context.CachedResponseHeaders != null)
{
for (var i = 0; i < HeadersToIncludeIn304.Length; i++)
{
var key = HeadersToIncludeIn304[i];
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();