Don't close connection when Content-Length set but no bytes written.

This commit is contained in:
Cesar Blum Silveira 2017-01-12 12:38:15 -08:00
parent e26f91a392
commit 89a63d190e
3 changed files with 29 additions and 23 deletions

View File

@ -695,7 +695,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
responseHeaders.HeaderContentLengthValue.HasValue && responseHeaders.HeaderContentLengthValue.HasValue &&
_responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value) _responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value)
{ {
_keepAlive = false; // We need to close the connection if any bytes were written since the client
// cannot be certain of how many bytes it will receive.
if (_responseBytesWritten > 0)
{
_keepAlive = false;
}
ReportApplicationError(new InvalidOperationException( ReportApplicationError(new InvalidOperationException(
$"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value}).")); $"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value})."));
} }

View File

@ -668,7 +668,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
[Fact] [Fact]
public async Task WhenAppSetsContentLengthButDoesNotWriteBody500ResponseSentAndConnectionCloses() public async Task WhenAppSetsContentLengthButDoesNotWriteBody500ResponseSentAndConnectionDoesNotClose()
{ {
var testLogger = new TestApplicationErrorLogger(); var testLogger = new TestApplicationErrorLogger();
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) }; var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
@ -682,12 +682,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
await connection.Send( await connection.Send(
"GET / HTTP/1.1",
"",
"GET / HTTP/1.1", "GET / HTTP/1.1",
"", "",
""); "");
await connection.ReceiveForcedEnd( await connection.Receive(
$"HTTP/1.1 500 Internal Server Error", "HTTP/1.1 500 Internal Server Error",
"Connection: close", $"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"HTTP/1.1 500 Internal Server Error",
$"Date: {server.Context.DateHeaderValue}", $"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0", "Content-Length: 0",
"", "",
@ -695,10 +700,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
} }
var errorMessage = Assert.Single(testLogger.Messages, message => message.LogLevel == LogLevel.Error); var error = testLogger.Messages.Where(message => message.LogLevel == LogLevel.Error);
Assert.Equal( Assert.Equal(2, error.Count());
$"Response Content-Length mismatch: too few bytes written (0 of 5).", Assert.All(error, message => message.Equals("Response Content-Length mismatch: too few bytes written (0 of 5)."));
errorMessage.Exception.Message);
} }
[Theory] [Theory]

View File

@ -853,12 +853,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[MemberData(nameof(ConnectionAdapterData))] [MemberData(nameof(ConnectionAdapterData))]
public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(ListenOptions listenOptions) public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(ListenOptions listenOptions)
{ {
var callback1Called = false;
var callback2CallCount = 0;
var testContext = new TestServiceContext(); var testContext = new TestServiceContext();
var onStartingCallCount1 = 0;
var onStartingCallCount2 = 0;
var failedWriteCount = 0;
var testLogger = new TestApplicationErrorLogger(); var testLogger = new TestApplicationErrorLogger();
testContext.Log = new KestrelTrace(testLogger); testContext.Log = new KestrelTrace(testLogger);
@ -869,19 +867,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var response = httpContext.Response; var response = httpContext.Response;
response.OnStarting(_ => response.OnStarting(_ =>
{ {
onStartingCallCount1++; callback1Called = true;
throw onStartingException; throw onStartingException;
}, null); }, null);
response.OnStarting(_ => response.OnStarting(_ =>
{ {
onStartingCallCount2++; callback2CallCount++;
throw onStartingException; throw onStartingException;
}, null); }, null);
var writeException = await Assert.ThrowsAsync<ObjectDisposedException>(async () => await response.Body.FlushAsync()); var writeException = await Assert.ThrowsAsync<ObjectDisposedException>(async () => await response.Body.FlushAsync());
Assert.Same(onStartingException, writeException.InnerException); Assert.Same(onStartingException, writeException.InnerException);
failedWriteCount++;
}, testContext, listenOptions)) }, testContext, listenOptions))
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
@ -892,7 +888,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"GET / HTTP/1.1", "GET / HTTP/1.1",
"", "",
""); "");
await connection.Receive( await connection.ReceiveEnd(
"HTTP/1.1 500 Internal Server Error", "HTTP/1.1 500 Internal Server Error",
$"Date: {testContext.DateHeaderValue}", $"Date: {testContext.DateHeaderValue}",
"Content-Length: 0", "Content-Length: 0",
@ -905,10 +901,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
} }
// The first registered OnStarting callback should not be called, // The first registered OnStarting callback should have been called,
// since they are called LIFO and the other one failed. // since they are called LIFO order and the other one failed.
Assert.Equal(0, onStartingCallCount1); Assert.False(callback1Called);
Assert.Equal(2, onStartingCallCount2); Assert.Equal(2, callback2CallCount);
Assert.Equal(2, testLogger.ApplicationErrorsLogged); Assert.Equal(2, testLogger.ApplicationErrorsLogged);
} }