Separate request rejection from bad request state setting.
This commit is contained in:
parent
f1071dea50
commit
78584799a4
|
|
@ -47,7 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private readonly object _onStartingSync = new Object();
|
||||
private readonly object _onCompletedSync = new Object();
|
||||
|
||||
protected bool _requestRejected;
|
||||
private Streams _frameStreams;
|
||||
|
||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
||||
|
|
@ -64,6 +63,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private bool _canHaveBody;
|
||||
private bool _autoChunk;
|
||||
protected Exception _applicationException;
|
||||
private BadHttpRequestException _requestRejectedException;
|
||||
|
||||
protected HttpVersion _httpVersion;
|
||||
|
||||
|
|
@ -717,7 +717,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
protected Task TryProduceInvalidRequestResponse()
|
||||
{
|
||||
if (_requestRejected)
|
||||
if (_requestRejectedException != null)
|
||||
{
|
||||
if (FrameRequestHeaders == null || FrameResponseHeaders == null)
|
||||
{
|
||||
|
|
@ -732,7 +732,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
protected Task ProduceEnd()
|
||||
{
|
||||
if (_requestRejected || _applicationException != null)
|
||||
if (_requestRejectedException != null || _applicationException != null)
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
|
|
@ -741,8 +741,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
// If the request was rejected, the error state has already been set by SetBadRequestState
|
||||
if (!_requestRejected)
|
||||
// If the request was rejected, the error state has already been set by SetBadRequestState and
|
||||
// that should take precedence.
|
||||
if (_requestRejectedException != null)
|
||||
{
|
||||
SetErrorResponseHeaders(statusCode: _requestRejectedException.StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 500 Internal Server Error
|
||||
SetErrorResponseHeaders(statusCode: 500);
|
||||
|
|
@ -1394,24 +1399,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
_applicationException);
|
||||
}
|
||||
|
||||
public void RejectRequest(string message)
|
||||
{
|
||||
throw new ObjectDisposedException(
|
||||
"The response has been aborted due to an unhandled application exception.",
|
||||
_applicationException);
|
||||
}
|
||||
|
||||
public void RejectRequest(RequestRejectionReason reason)
|
||||
{
|
||||
var ex = BadHttpRequestException.GetException(reason);
|
||||
SetBadRequestState(ex);
|
||||
throw ex;
|
||||
RejectRequest(BadHttpRequestException.GetException(reason));
|
||||
}
|
||||
|
||||
public void RejectRequest(RequestRejectionReason reason, string value)
|
||||
{
|
||||
var ex = BadHttpRequestException.GetException(reason, value);
|
||||
SetBadRequestState(ex);
|
||||
RejectRequest(BadHttpRequestException.GetException(reason, value));
|
||||
}
|
||||
|
||||
private void RejectRequest(BadHttpRequestException ex)
|
||||
{
|
||||
Log.ConnectionBadRequest(ConnectionId, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
|
@ -1422,17 +1422,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
public void SetBadRequestState(BadHttpRequestException ex)
|
||||
{
|
||||
// Setting status code will throw if response has already started
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
SetErrorResponseHeaders(statusCode: ex.StatusCode);
|
||||
SetErrorResponseHeaders(ex.StatusCode);
|
||||
}
|
||||
|
||||
_keepAlive = false;
|
||||
_requestProcessingStopping = true;
|
||||
_requestRejected = true;
|
||||
|
||||
Log.ConnectionBadRequest(ConnectionId, ex);
|
||||
_requestRejectedException = ex;
|
||||
}
|
||||
|
||||
protected void ReportApplicationError(Exception ex)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
catch (Exception ex)
|
||||
{
|
||||
ReportApplicationError(ex);
|
||||
|
||||
if (ex is BadHttpRequestException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -118,6 +123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
await FireOnCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
// If _requestAbort is set, the connection has already been closed.
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
|
|
@ -142,6 +148,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
StatusCode = 0;
|
||||
}
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
// Handle BadHttpRequestException thrown during app execution or remaining message body consumption.
|
||||
// This has to be caught here so StatusCode is set properly before disposing the HttpContext
|
||||
// (DisposeContext logs StatusCode).
|
||||
SetBadRequestState(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -169,12 +181,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
if (!_requestRejected)
|
||||
{
|
||||
// Handle BadHttpRequestException thrown during request line or header parsing.
|
||||
// SetBadRequestState logs the error.
|
||||
SetBadRequestState(ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.LogWarning(0, ex, "Connection processing ended abnormally");
|
||||
|
|
@ -183,11 +193,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
try
|
||||
{
|
||||
await TryProduceInvalidRequestResponse();
|
||||
|
||||
// If _requestAborted is set, the connection has already been closed.
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
{
|
||||
await TryProduceInvalidRequestResponse();
|
||||
ConnectionControl.End(ProduceEndType.SocketShutdown);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
|
@ -239,6 +238,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
sendMalformedRequest: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task ResponseStatusCodeSetBeforeHttpContextDisposedRequestMalformedRead()
|
||||
{
|
||||
return ResponseStatusCodeSetBeforeHttpContextDispose(
|
||||
async context =>
|
||||
{
|
||||
await context.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
},
|
||||
expectedClientStatusCode: null,
|
||||
expectedServerStatusCode: HttpStatusCode.BadRequest,
|
||||
sendMalformedRequest: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task ResponseStatusCodeSetBeforeHttpContextDisposedRequestMalformedReadIgnored()
|
||||
{
|
||||
return ResponseStatusCodeSetBeforeHttpContextDispose(
|
||||
async context =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
}
|
||||
catch (BadHttpRequestException)
|
||||
{
|
||||
}
|
||||
},
|
||||
expectedClientStatusCode: null,
|
||||
expectedServerStatusCode: HttpStatusCode.BadRequest,
|
||||
sendMalformedRequest: true);
|
||||
}
|
||||
|
||||
private static async Task ResponseStatusCodeSetBeforeHttpContextDispose(
|
||||
RequestDelegate handler,
|
||||
HttpStatusCode? expectedClientStatusCode,
|
||||
|
|
@ -730,14 +761,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
[Fact]
|
||||
public async Task HeadResponseCanContainContentLengthHeader()
|
||||
{
|
||||
var testLogger = new TestApplicationErrorLogger();
|
||||
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
|
||||
|
||||
using (var server = new TestServer(httpContext =>
|
||||
{
|
||||
httpContext.Response.ContentLength = 42;
|
||||
return TaskCache.CompletedTask;
|
||||
}, serviceContext))
|
||||
}, new TestServiceContext()))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -746,7 +774,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
"",
|
||||
"");
|
||||
await connection.ReceiveEnd(
|
||||
$"HTTP/1.1 200 OK",
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 42",
|
||||
"",
|
||||
|
|
@ -758,14 +786,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
[Fact]
|
||||
public async Task HeadResponseCanContainContentLengthHeaderButBodyNotWritten()
|
||||
{
|
||||
var testLogger = new TestApplicationErrorLogger();
|
||||
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
httpContext.Response.ContentLength = 12;
|
||||
await httpContext.Response.WriteAsync("hello, world");
|
||||
}, serviceContext))
|
||||
}, new TestServiceContext()))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -774,7 +799,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
"",
|
||||
"");
|
||||
await connection.ReceiveEnd(
|
||||
$"HTTP/1.1 200 OK",
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 12",
|
||||
"",
|
||||
|
|
@ -783,6 +808,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppCanWriteOwnBadRequestResponse()
|
||||
{
|
||||
var expectedResponse = string.Empty;
|
||||
var responseWrittenTcs = new TaskCompletionSource<object>();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
expectedResponse = ex.Message;
|
||||
httpContext.Response.StatusCode = 400;
|
||||
httpContext.Response.ContentLength = ex.Message.Length;
|
||||
await httpContext.Response.WriteAsync(ex.Message);
|
||||
responseWrittenTcs.SetResult(null);
|
||||
}
|
||||
}, new TestServiceContext()))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.SendEnd(
|
||||
"POST / HTTP/1.1",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"bad");
|
||||
await responseWrittenTcs.Task;
|
||||
await connection.ReceiveEnd(
|
||||
"HTTP/1.1 400 Bad Request",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
$"Content-Length: {expectedResponse.Length}",
|
||||
"",
|
||||
expectedResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string, StringValues, string> NullHeaderData
|
||||
{
|
||||
get
|
||||
|
|
|
|||
Loading…
Reference in New Issue