From 972be6e8c18d1ca1b0b8435dd6ba4526154df100 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 17 Aug 2016 11:10:39 -0700 Subject: [PATCH] Disable automatic chunking for all non keep-alive requests - Fixes a bug where Upgrade requests (e.g. WebSockets) would be chunked - Allows chunking to be disabled by setting "Connection: close" on the response --- .../Internal/Http/Frame.cs | 12 +-- .../ChunkedResponseTests.cs | 82 +++++++++++-------- .../EngineTests.cs | 40 ++++++++- 3 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs index aad34b77dd..7cc160f0ff 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs @@ -723,14 +723,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http var end = SocketOutput.ProducingStart(); if (_keepAlive && hasConnection) { - foreach (var connectionValue in responseHeaders.HeaderConnection) - { - if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1) - { - _keepAlive = false; - break; - } - } + var connectionValue = responseHeaders.HeaderConnection.ToString(); + _keepAlive = connectionValue.Equals("keep-alive", StringComparison.OrdinalIgnoreCase); } if (!responseHeaders.HasTransferEncoding && !responseHeaders.HasContentLength) @@ -746,7 +740,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http responseHeaders.SetRawContentLength("0", _bytesContentLengthZero); } } - else + else if(_keepAlive) { // Note for future reference: never change this to set _autoChunk to true on HTTP/1.0 // connections, even if we were to infer the client supports it because an HTTP/1.0 request diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs index 1f970fa9cf..8e9d12e4dd 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs @@ -63,42 +63,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ResponsesAreChunkedAutomaticallyForHttp11NonKeepAliveRequests(TestServiceContext testContext) - { - using (var server = new TestServer(async httpContext => - { - var response = httpContext.Response; - await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); - await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); - }, testContext)) - { - using (var connection = server.CreateConnection()) - { - await connection.SendEnd( - "GET / HTTP/1.1", - "Connection: close", - "", - ""); - await connection.ReceiveEnd( - "HTTP/1.1 200 OK", - "Connection: close", - $"Date: {testContext.DateHeaderValue}", - "Transfer-Encoding: chunked", - "", - "6", - "Hello ", - "6", - "World!", - "0", - "", - ""); - } - } - } - - [Theory] - [MemberData(nameof(ConnectionFilterData))] - public async Task ResponsesAreNotChunkedAutomaticallyForHttp10Requests(TestServiceContext testContext) + public async Task ResponsesAreNotChunkedAutomaticallyForHttp10RequestsAndHttp11NonKeepAliveRequests(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { @@ -111,6 +76,51 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { await connection.SendEnd( "GET / HTTP/1.0", + "Connection: keep-alive", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {testContext.DateHeaderValue}", + "", + "Hello World!"); + } + + using (var connection = server.CreateConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.1", + "Connection: close", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {testContext.DateHeaderValue}", + "", + "Hello World!"); + } + } + } + + + [Theory] + [MemberData(nameof(ConnectionFilterData))] + public async Task SettingConnectionCloseHeaderInAppDisablesChunking(TestServiceContext testContext) + { + using (var server = new TestServer(async httpContext => + { + var response = httpContext.Response; + response.Headers["Connection"] = "close"; + await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); + await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); + }, testContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.1", "", ""); await connection.ReceiveEnd( diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs index 80e59304f8..eb295aadb2 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs @@ -120,7 +120,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests engine.Dispose(); } - [Theory] [MemberData(nameof(ConnectionFilterData))] public async Task Http10RequestReceivesHttp11Response(TestServiceContext testContext) @@ -143,7 +142,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } - [Theory] [MemberData(nameof(ConnectionFilterData))] public async Task Http11(TestServiceContext testContext) @@ -1310,5 +1308,43 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } } + + [Theory] + [MemberData(nameof(ConnectionFilterData))] + public async Task UpgradeRequestIsNotKeptAliveOrChunked(TestServiceContext testContext) + { + using (var server = new TestServer(async context => + { + var upgradeFeature = context.Features.Get(); + var duplexStream = await upgradeFeature.UpgradeAsync(); + + while (true) + { + var buffer = new byte[8192]; + var count = await duplexStream.ReadAsync(buffer, 0, buffer.Length); + if (count == 0) + { + break; + } + await duplexStream.WriteAsync(buffer, 0, count); + } + }, testContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.1", + "Connection: Upgrade", + "", + "Hello World"); + await connection.ReceiveEnd( + "HTTP/1.1 101 Switching Protocols", + "Connection: Upgrade", + $"Date: {testContext.DateHeaderValue}", + "", + "Hello World"); + } + } + } } }