From 2642c84bf99190eed5f454e6c37b88b913dbf596 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 26 Aug 2015 12:41:14 -0700 Subject: [PATCH] Don't automatically set Content-Length: 0 in some circumstances - When in response to a HEAD Request - For 101, 204, 205 and 304 responses - For non keep-alive connections --- .../Http/Frame.cs | 40 ++++-- .../EngineTests.cs | 124 +++++++++++++++++- 2 files changed, 145 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index a78d7f7eae..d5dc958080 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -494,26 +494,33 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } } - if (appCompleted && !hasTransferEncoding && !hasContentLength) - { - // Since the app has completed and we are only now generating - // the headers we can safely set the Content-Length to 0. - writer.Write("Content-Length: 0\r\n"); - hasContentLength = true; - } - if (_keepAlive && !hasTransferEncoding && !hasContentLength) { - if (HttpVersion == "HTTP/1.1") + if (appCompleted) { - _autoChunk = true; - writer.Write("Transfer-Encoding: chunked\r\n"); + // Don't set the Content-Length or Transfer-Encoding headers + // automatically for HEAD requests or 101, 204, 205, 304 responses. + if (Method != "HEAD" && StatusCanHaveBody(StatusCode)) + { + // Since the app has completed and we are only now generating + // the headers we can safely set the Content-Length to 0. + writer.Write("Content-Length: 0\r\n"); + } } else { - _keepAlive = false; + if (HttpVersion == "HTTP/1.1") + { + _autoChunk = true; + writer.Write("Transfer-Encoding: chunked\r\n"); + } + else + { + _keepAlive = false; + } } } + if (_keepAlive == false && hasConnection == false && HttpVersion == "HTTP/1.1") { writer.Write("Connection: close\r\n\r\n"); @@ -673,5 +680,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { _requestHeaders.Append(keyBytes, keyOffset, keyLength, value); } + + public bool StatusCanHaveBody(int statusCode) + { + // List of status codes taken from Microsoft.Net.Http.Server.Response + return statusCode != 101 && + statusCode != 204 && + statusCode != 205 && + statusCode != 304; + } } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs index d36d02aee8..a293e8bae9 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs @@ -77,6 +77,12 @@ namespace Microsoft.AspNet.Server.KestrelTests await frame.ResponseBody.WriteAsync(bytes, 0, bytes.Length); } + private Task EmptyApp(Frame frame) + { + frame.ResponseHeaders.Clear(); + return Task.FromResult(null); + } + [Fact] public void EngineCanStartAndStop() { @@ -370,28 +376,132 @@ namespace Microsoft.AspNet.Server.KestrelTests "\r\n"); await connection.ReceiveEnd( "HTTP/1.0 200 OK", - "Content-Length: 0", "\r\n"); } } } [Fact] - public async Task EmptyResponseBodyHandledCorrectlyWithoutAnyWrites() + public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites() { - using (var server = new TestServer(frame => - { - frame.ResponseHeaders.Clear(); - return Task.FromResult(null); - })) + using (var server = new TestServer(EmptyApp)) { using (var connection = new TestConnection()) { await connection.SendEnd( "GET / HTTP/1.1", "", + "GET / HTTP/1.0", + "Connection: keep-alive", + "", ""); await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Content-Length: 0", + "", + "HTTP/1.0 200 OK", + "Content-Length: 0", + "Connection: keep-alive", + "", + ""); + } + } + } + + [Fact] + public async Task ZeroContentLengthNotSetAutomaticallyForNonKeepAliveRequests() + { + using (var server = new TestServer(EmptyApp)) + { + using (var connection = new TestConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.1", + "Connection: close", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Connection: close", + "", + ""); + } + + using (var connection = new TestConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.0", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.0 200 OK", + "", + ""); + } + } + } + + [Fact] + public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests() + { + using (var server = new TestServer(EmptyApp)) + { + using (var connection = new TestConnection()) + { + await connection.SendEnd( + "HEAD / HTTP/1.1", + "", + ""); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "", + ""); + } + } + } + + [Fact] + public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes() + { + using (var server = new TestServer(async frame => + { + frame.ResponseHeaders.Clear(); + + using (var reader = new StreamReader(frame.RequestBody, Encoding.ASCII)) + { + var statusString = await reader.ReadLineAsync(); + frame.StatusCode = int.Parse(statusString); + } + })) + { + using (var connection = new TestConnection()) + { + await connection.SendEnd( + "POST / HTTP/1.1", + "Content-Length: 3", + "", + "101POST / HTTP/1.1", + "Content-Length: 3", + "", + "204POST / HTTP/1.1", + "Content-Length: 3", + "", + "205POST / HTTP/1.1", + "Content-Length: 3", + "", + "304POST / HTTP/1.1", + "Content-Length: 3", + "", + "200"); + await connection.ReceiveEnd( + "HTTP/1.1 101 Switching Protocols", + "", + "HTTP/1.1 204 No Content", + "", + "HTTP/1.1 205 Reset Content", + "", + "HTTP/1.1 304 Not Modified", + "", "HTTP/1.1 200 OK", "Content-Length: 0", "",