From 6855f9bca19e88603ae065a1f62c9d763c010b2b Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 11 Jan 2017 15:35:18 -0800 Subject: [PATCH] Add Vary: Accept-Encoding if response MIME type is compressible --- .../BodyWrapperStream.cs | 18 +++++++ .../BodyWrapperStreamTests.cs | 20 ++++++++ .../ResponseCompressionMiddlewareTest.cs | 49 ++++++++++++++----- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.AspNetCore.ResponseCompression/BodyWrapperStream.cs b/src/Microsoft.AspNetCore.ResponseCompression/BodyWrapperStream.cs index 93197fa1..4906e0e2 100644 --- a/src/Microsoft.AspNetCore.ResponseCompression/BodyWrapperStream.cs +++ b/src/Microsoft.AspNetCore.ResponseCompression/BodyWrapperStream.cs @@ -211,6 +211,24 @@ private void OnWrite() _compressionChecked = true; if (_provider.ShouldCompressResponse(_context)) { + // If the MIME type indicates that the response could be compressed, caches will need to vary by the Accept-Encoding header + var varyValues = _context.Response.Headers.GetCommaSeparatedValues(HeaderNames.Vary); + var varyByAcceptEncoding = false; + + for (var i = 0; i < varyValues.Length; i++) + { + if (string.Equals(varyValues[i], HeaderNames.AcceptEncoding, StringComparison.OrdinalIgnoreCase)) + { + varyByAcceptEncoding = true; + break; + } + } + + if (!varyByAcceptEncoding) + { + _context.Response.Headers.Append(HeaderNames.Vary, HeaderNames.AcceptEncoding); + } + var compressionProvider = ResolveCompressionProvider(); if (compressionProvider != null) { diff --git a/test/Microsoft.AspNetCore.ResponseCompression.Tests/BodyWrapperStreamTests.cs b/test/Microsoft.AspNetCore.ResponseCompression.Tests/BodyWrapperStreamTests.cs index 75ca3d7d..f5aa7035 100644 --- a/test/Microsoft.AspNetCore.ResponseCompression.Tests/BodyWrapperStreamTests.cs +++ b/test/Microsoft.AspNetCore.ResponseCompression.Tests/BodyWrapperStreamTests.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -14,6 +15,25 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests { public class BodyWrapperStreamTests { + [Theory] + [InlineData(null, "Accept-Encoding")] + [InlineData("", "Accept-Encoding")] + [InlineData("AnotherHeader", "AnotherHeader,Accept-Encoding")] + [InlineData("Accept-Encoding", "Accept-Encoding")] + [InlineData("accepT-encodinG", "accepT-encodinG")] + [InlineData("accept-encoding,AnotherHeader", "accept-encoding,AnotherHeader")] + public void OnWrite_AppendsAcceptEncodingToVaryHeader_IfNotPresent(string providedVaryHeader, string expectedVaryHeader) + { + var httpContext = new DefaultHttpContext(); + httpContext.Response.Headers[HeaderNames.Vary] = providedVaryHeader; + var stream = new BodyWrapperStream(httpContext, new MemoryStream(), new MockResponseCompressionProvider(flushable: true), null, null); + + stream.Write(new byte[] { }, 0, 0); + + + Assert.Equal(expectedVaryHeader, httpContext.Response.Headers[HeaderNames.Vary]); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs b/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs index 9c26de63..8328716c 100644 --- a/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs @@ -36,7 +36,7 @@ public async Task Request_NoAcceptEncoding_Uncompressed() { var response = await InvokeMiddleware(100, requestAcceptEncodings: null, responseType: TextPlain); - CheckResponseNotCompressed(response, expectedBodyLength: 100); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); } [Fact] @@ -52,7 +52,7 @@ public async Task Request_AcceptUnknown_NotCompressed() { var response = await InvokeMiddleware(100, requestAcceptEncodings: new string[] { "unknown" }, responseType: TextPlain); - CheckResponseNotCompressed(response, expectedBodyLength: 100); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); } [Theory] @@ -149,7 +149,7 @@ public async Task MimeTypes_OtherContentTypes_NoMatch(string contentType) var response = await client.SendAsync(request); - CheckResponseNotCompressed(response, expectedBodyLength: 100); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); } [Theory] @@ -185,7 +185,7 @@ public async Task NoBody_NotCompressed(string contentType) var response = await client.SendAsync(request); - CheckResponseNotCompressed(response, expectedBodyLength: 0); + CheckResponseNotCompressed(response, expectedBodyLength: 0, sendVaryHeader: false); } [Fact] @@ -201,7 +201,7 @@ public async Task Request_AcceptIdentity_NotCompressed() { var response = await InvokeMiddleware(100, requestAcceptEncodings: new string[] { "identity" }, responseType: TextPlain); - CheckResponseNotCompressed(response, expectedBodyLength: 100); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); } [Theory] @@ -221,7 +221,7 @@ public async Task Request_AcceptWithhigherIdentityQuality_NotCompressed(string[] { var response = await InvokeMiddleware(100, requestAcceptEncodings: acceptEncodings, responseType: TextPlain); - CheckResponseNotCompressed(response, expectedBodyLength: expectedBodyLength); + CheckResponseNotCompressed(response, expectedBodyLength: expectedBodyLength, sendVaryHeader: true); } [Fact] @@ -229,7 +229,7 @@ public async Task Response_UnknownMimeType_NotCompressed() { var response = await InvokeMiddleware(100, requestAcceptEncodings: new string[] { "gzip" }, responseType: "text/custom"); - CheckResponseNotCompressed(response, expectedBodyLength: 100); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); } [Fact] @@ -240,7 +240,7 @@ public async Task Response_WithContentRange_NotCompressed() r.Headers[HeaderNames.ContentRange] = "1-2/*"; }); - CheckResponseNotCompressed(response, expectedBodyLength: 50); + CheckResponseNotCompressed(response, expectedBodyLength: 50, sendVaryHeader: false); } [Fact] @@ -446,7 +446,7 @@ public async Task FlushAsyncBody_CompressesAndFlushes() request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - + IEnumerable contentMD5 = null; Assert.False(response.Headers.TryGetValues(HeaderNames.ContentMD5, out contentMD5)); Assert.Single(response.Content.Headers.ContentEncoding, "gzip"); @@ -662,7 +662,7 @@ public async Task SendFileAsync_DifferentContentType_NotBypassed() var response = await client.SendAsync(request); - CheckResponseNotCompressed(response, expectedBodyLength: 1024); + CheckResponseNotCompressed(response, expectedBodyLength: 1024, sendVaryHeader: false); Assert.True(fakeSendFile.Invoked); } @@ -793,13 +793,40 @@ private void CheckResponseCompressed(HttpResponseMessage response, int expectedB { IEnumerable contentMD5 = null; + var containsVaryAcceptEncoding = false; + foreach (var value in response.Headers.GetValues(HeaderNames.Vary)) + { + if (value.Contains(HeaderNames.AcceptEncoding)) + { + containsVaryAcceptEncoding = true; + break; + } + } + Assert.True(containsVaryAcceptEncoding); Assert.False(response.Headers.TryGetValues(HeaderNames.ContentMD5, out contentMD5)); Assert.Single(response.Content.Headers.ContentEncoding, "gzip"); Assert.Equal(expectedBodyLength, response.Content.Headers.ContentLength); } - private void CheckResponseNotCompressed(HttpResponseMessage response, int expectedBodyLength) + private void CheckResponseNotCompressed(HttpResponseMessage response, int expectedBodyLength, bool sendVaryHeader) { + if (sendVaryHeader) + { + var containsVaryAcceptEncoding = false; + foreach (var value in response.Headers.GetValues(HeaderNames.Vary)) + { + if (value.Contains(HeaderNames.AcceptEncoding)) + { + containsVaryAcceptEncoding = true; + break; + } + } + Assert.True(containsVaryAcceptEncoding); + } + else + { + Assert.False(response.Headers.Contains(HeaderNames.Vary)); + } Assert.NotNull(response.Headers.GetValues(HeaderNames.ContentMD5)); Assert.Empty(response.Content.Headers.ContentEncoding); Assert.Equal(expectedBodyLength, response.Content.Headers.ContentLength);