diff --git a/src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs b/src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs index 2eefdb6482b8..ef74ba89164c 100644 --- a/src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs +++ b/src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs @@ -26,14 +26,16 @@ internal sealed class ResponseCompressionBody : Stream, IHttpResponseBodyFeature private bool _providerCreated; private bool _autoFlush; private bool _complete; + private readonly bool _allowCompression; internal ResponseCompressionBody(HttpContext context, IResponseCompressionProvider provider, - IHttpResponseBodyFeature innerBodyFeature) + IHttpResponseBodyFeature innerBodyFeature, bool allowCompression) { _context = context; _provider = provider; _innerBodyFeature = innerBodyFeature; _innerStream = innerBodyFeature.Stream; + _allowCompression = allowCompression; } internal async Task FinishCompressionAsync() @@ -224,17 +226,22 @@ public override async ValueTask WriteAsync(ReadOnlyMemory buffer, Cancella headers.Vary = StringValues.Concat(headers.Vary, HeaderNames.AcceptEncoding); } - var compressionProvider = ResolveCompressionProvider(); - if (compressionProvider != null) + // Only attempt compression when the request includes Accept-Encoding. + // The Vary header is always added above for correct caching behavior. + if (_allowCompression) { - // Can't use += as StringValues does not override operator+ - // and the implict conversions will cause an incorrect string concat https://github.com/dotnet/runtime/issues/52507 - headers.ContentEncoding = StringValues.Concat(headers.ContentEncoding, compressionProvider.EncodingName); - headers.ContentMD5 = default; // Reset the MD5 because the content changed. - headers.ContentLength = default; - } + var compressionProvider = ResolveCompressionProvider(); + if (compressionProvider != null) + { + // Can't use += as StringValues does not override operator+ + // and the implict conversions will cause an incorrect string concat https://github.com/dotnet/runtime/issues/52507 + headers.ContentEncoding = StringValues.Concat(headers.ContentEncoding, compressionProvider.EncodingName); + headers.ContentMD5 = default; // Reset the MD5 because the content changed. + headers.ContentLength = default; + } - return compressionProvider; + return compressionProvider; + } } return null; diff --git a/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs b/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs index 22a9677d6e10..4a67f0418fa0 100644 --- a/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs +++ b/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs @@ -36,21 +36,17 @@ public ResponseCompressionMiddleware(RequestDelegate next, IResponseCompressionP /// A task that represents the execution of this middleware. public Task Invoke(HttpContext context) { - if (!_provider.CheckRequestAcceptsCompression(context)) - { - return _next(context); - } - return InvokeCore(context); + return InvokeCore(context, _provider.CheckRequestAcceptsCompression(context)); } - private async Task InvokeCore(HttpContext context) + private async Task InvokeCore(HttpContext context, bool allowCompression) { var originalBodyFeature = context.Features.Get(); var originalCompressionFeature = context.Features.Get(); Debug.Assert(originalBodyFeature != null); - var compressionBody = new ResponseCompressionBody(context, _provider, originalBodyFeature); + var compressionBody = new ResponseCompressionBody(context, _provider, originalBodyFeature, allowCompression); context.Features.Set(compressionBody); context.Features.Set(compressionBody); diff --git a/src/Middleware/ResponseCompression/test/ResponseCompressionBodyTest.cs b/src/Middleware/ResponseCompression/test/ResponseCompressionBodyTest.cs index 0999ddd07b8e..711d85897e6e 100644 --- a/src/Middleware/ResponseCompression/test/ResponseCompressionBodyTest.cs +++ b/src/Middleware/ResponseCompression/test/ResponseCompressionBodyTest.cs @@ -18,7 +18,7 @@ public void OnWrite_AppendsAcceptEncodingToVaryHeader_IfNotPresent(string provid { var httpContext = new DefaultHttpContext(); httpContext.Response.Headers.Vary = providedVaryHeader; - var stream = new ResponseCompressionBody(httpContext, new MockResponseCompressionProvider(flushable: true), new StreamResponseBodyFeature(new MemoryStream())); + var stream = new ResponseCompressionBody(httpContext, new MockResponseCompressionProvider(flushable: true), new StreamResponseBodyFeature(new MemoryStream()), allowCompression: true); stream.Write(new byte[] { }, 0, 0); @@ -34,7 +34,7 @@ public void Write_IsPassedToUnderlyingStream_WhenDisableResponseBuffering(bool f var buffer = new byte[] { 1 }; var memoryStream = new MemoryStream(); - var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(flushable), new StreamResponseBodyFeature(memoryStream)); + var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(flushable), new StreamResponseBodyFeature(memoryStream), allowCompression: true); stream.DisableBuffering(); stream.Write(buffer, 0, buffer.Length); @@ -50,7 +50,7 @@ public async Task WriteAsync_IsPassedToUnderlyingStream_WhenDisableResponseBuffe var buffer = new byte[] { 1 }; var memoryStream = new MemoryStream(); - var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(flushable), new StreamResponseBodyFeature(memoryStream)); + var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(flushable), new StreamResponseBodyFeature(memoryStream), allowCompression: true); stream.DisableBuffering(); await stream.WriteAsync(buffer, 0, buffer.Length); @@ -63,7 +63,7 @@ public async Task SendFileAsync_IsPassedToUnderlyingStream_WhenDisableResponseBu { var memoryStream = new MemoryStream(); - var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(true), new StreamResponseBodyFeature(memoryStream)); + var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(true), new StreamResponseBodyFeature(memoryStream), allowCompression: true); stream.DisableBuffering(); @@ -82,7 +82,7 @@ public void BeginWrite_IsPassedToUnderlyingStream_WhenDisableResponseBuffering(b var memoryStream = new MemoryStream(); - var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(flushable), new StreamResponseBodyFeature(memoryStream)); + var stream = new ResponseCompressionBody(new DefaultHttpContext(), new MockResponseCompressionProvider(flushable), new StreamResponseBodyFeature(memoryStream), allowCompression: true); stream.DisableBuffering(); stream.BeginWrite(buffer, 0, buffer.Length, (o) => { }, null); diff --git a/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs b/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs index 4f1d1def09fc..983b8e04b99f 100644 --- a/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs +++ b/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs @@ -45,8 +45,8 @@ public async Task Request_NoAcceptEncoding_Uncompressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: null, responseType: TextPlain); - CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); - AssertLog(logMessages.Single(), LogLevel.Debug, "No response compression available, the Accept-Encoding header is missing or invalid."); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); + Assert.Contains(logMessages, lm => lm.LogLevel == LogLevel.Debug && lm.State.ToString().Contains("Accept-Encoding header is missing or invalid")); } [Fact] @@ -136,10 +136,9 @@ public async Task RequestHead_NoAcceptEncoding_Uncompressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: null, responseType: TextPlain, httpMethod: HttpMethods.Head); - CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); - AssertLog(logMessages.Single(), LogLevel.Debug, "No response compression available, the Accept-Encoding header is missing or invalid."); + CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); + Assert.Contains(logMessages, lm => lm.LogLevel == LogLevel.Debug && lm.State.ToString().Contains("Accept-Encoding header is missing or invalid")); } - [Fact] public async Task RequestHead_AcceptGzipDeflate_CompressedGzip() {