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 _onStartingSync = new Object();
|
||||||
private readonly object _onCompletedSync = new Object();
|
private readonly object _onCompletedSync = new Object();
|
||||||
|
|
||||||
protected bool _requestRejected;
|
|
||||||
private Streams _frameStreams;
|
private Streams _frameStreams;
|
||||||
|
|
||||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
||||||
|
|
@ -64,6 +63,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
private bool _canHaveBody;
|
private bool _canHaveBody;
|
||||||
private bool _autoChunk;
|
private bool _autoChunk;
|
||||||
protected Exception _applicationException;
|
protected Exception _applicationException;
|
||||||
|
private BadHttpRequestException _requestRejectedException;
|
||||||
|
|
||||||
protected HttpVersion _httpVersion;
|
protected HttpVersion _httpVersion;
|
||||||
|
|
||||||
|
|
@ -717,7 +717,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
protected Task TryProduceInvalidRequestResponse()
|
protected Task TryProduceInvalidRequestResponse()
|
||||||
{
|
{
|
||||||
if (_requestRejected)
|
if (_requestRejectedException != null)
|
||||||
{
|
{
|
||||||
if (FrameRequestHeaders == null || FrameResponseHeaders == null)
|
if (FrameRequestHeaders == null || FrameResponseHeaders == null)
|
||||||
{
|
{
|
||||||
|
|
@ -732,7 +732,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
protected Task ProduceEnd()
|
protected Task ProduceEnd()
|
||||||
{
|
{
|
||||||
if (_requestRejected || _applicationException != null)
|
if (_requestRejectedException != null || _applicationException != null)
|
||||||
{
|
{
|
||||||
if (HasResponseStarted)
|
if (HasResponseStarted)
|
||||||
{
|
{
|
||||||
|
|
@ -741,8 +741,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request was rejected, the error state has already been set by SetBadRequestState
|
// If the request was rejected, the error state has already been set by SetBadRequestState and
|
||||||
if (!_requestRejected)
|
// that should take precedence.
|
||||||
|
if (_requestRejectedException != null)
|
||||||
|
{
|
||||||
|
SetErrorResponseHeaders(statusCode: _requestRejectedException.StatusCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// 500 Internal Server Error
|
// 500 Internal Server Error
|
||||||
SetErrorResponseHeaders(statusCode: 500);
|
SetErrorResponseHeaders(statusCode: 500);
|
||||||
|
|
@ -1394,24 +1399,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
_applicationException);
|
_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)
|
public void RejectRequest(RequestRejectionReason reason)
|
||||||
{
|
{
|
||||||
var ex = BadHttpRequestException.GetException(reason);
|
RejectRequest(BadHttpRequestException.GetException(reason));
|
||||||
SetBadRequestState(ex);
|
|
||||||
throw ex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason, string value)
|
public void RejectRequest(RequestRejectionReason reason, string value)
|
||||||
{
|
{
|
||||||
var ex = BadHttpRequestException.GetException(reason, value);
|
RejectRequest(BadHttpRequestException.GetException(reason, value));
|
||||||
SetBadRequestState(ex);
|
}
|
||||||
|
|
||||||
|
private void RejectRequest(BadHttpRequestException ex)
|
||||||
|
{
|
||||||
|
Log.ConnectionBadRequest(ConnectionId, ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1422,17 +1422,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
public void SetBadRequestState(BadHttpRequestException ex)
|
public void SetBadRequestState(BadHttpRequestException ex)
|
||||||
{
|
{
|
||||||
// Setting status code will throw if response has already started
|
|
||||||
if (!HasResponseStarted)
|
if (!HasResponseStarted)
|
||||||
{
|
{
|
||||||
SetErrorResponseHeaders(statusCode: ex.StatusCode);
|
SetErrorResponseHeaders(ex.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
_keepAlive = false;
|
_keepAlive = false;
|
||||||
_requestProcessingStopping = true;
|
_requestProcessingStopping = true;
|
||||||
_requestRejected = true;
|
_requestRejectedException = ex;
|
||||||
|
|
||||||
Log.ConnectionBadRequest(ConnectionId, ex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ReportApplicationError(Exception ex)
|
protected void ReportApplicationError(Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ReportApplicationError(ex);
|
ReportApplicationError(ex);
|
||||||
|
|
||||||
|
if (ex is BadHttpRequestException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -118,30 +123,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
await FireOnCompleted();
|
await FireOnCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If _requestAbort is set, the connection has already been closed.
|
|
||||||
if (Volatile.Read(ref _requestAborted) == 0)
|
|
||||||
{
|
|
||||||
ResumeStreams();
|
|
||||||
|
|
||||||
if (_keepAlive)
|
|
||||||
{
|
|
||||||
// Finish reading the request body in case the app did not.
|
|
||||||
await messageBody.Consume();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProduceEnd() must be called before _application.DisposeContext(), to ensure
|
|
||||||
// HttpContext.Response.StatusCode is correctly set when
|
|
||||||
// IHttpContextFactory.Dispose(HttpContext) is called.
|
|
||||||
await ProduceEnd();
|
|
||||||
}
|
|
||||||
else if (!HasResponseStarted)
|
|
||||||
{
|
|
||||||
// If the request was aborted and no response was sent, there's no
|
|
||||||
// meaningful status code to log.
|
|
||||||
StatusCode = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If _requestAbort is set, the connection has already been closed.
|
||||||
|
if (Volatile.Read(ref _requestAborted) == 0)
|
||||||
|
{
|
||||||
|
ResumeStreams();
|
||||||
|
|
||||||
|
if (_keepAlive)
|
||||||
|
{
|
||||||
|
// Finish reading the request body in case the app did not.
|
||||||
|
await messageBody.Consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProduceEnd() must be called before _application.DisposeContext(), to ensure
|
||||||
|
// HttpContext.Response.StatusCode is correctly set when
|
||||||
|
// IHttpContextFactory.Dispose(HttpContext) is called.
|
||||||
|
await ProduceEnd();
|
||||||
|
}
|
||||||
|
else if (!HasResponseStarted)
|
||||||
|
{
|
||||||
|
// If the request was aborted and no response was sent, there's no
|
||||||
|
// meaningful status code to log.
|
||||||
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -169,11 +181,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
catch (BadHttpRequestException ex)
|
catch (BadHttpRequestException ex)
|
||||||
{
|
{
|
||||||
if (!_requestRejected)
|
// Handle BadHttpRequestException thrown during request line or header parsing.
|
||||||
{
|
// SetBadRequestState logs the error.
|
||||||
// SetBadRequestState logs the error.
|
SetBadRequestState(ex);
|
||||||
SetBadRequestState(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -183,11 +193,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await TryProduceInvalidRequestResponse();
|
|
||||||
|
|
||||||
// If _requestAborted is set, the connection has already been closed.
|
// If _requestAborted is set, the connection has already been closed.
|
||||||
if (Volatile.Read(ref _requestAborted) == 0)
|
if (Volatile.Read(ref _requestAborted) == 0)
|
||||||
{
|
{
|
||||||
|
await TryProduceInvalidRequestResponse();
|
||||||
ConnectionControl.End(ProduceEndType.SocketShutdown);
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
@ -239,6 +238,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
sendMalformedRequest: true);
|
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(
|
private static async Task ResponseStatusCodeSetBeforeHttpContextDispose(
|
||||||
RequestDelegate handler,
|
RequestDelegate handler,
|
||||||
HttpStatusCode? expectedClientStatusCode,
|
HttpStatusCode? expectedClientStatusCode,
|
||||||
|
|
@ -730,14 +761,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HeadResponseCanContainContentLengthHeader()
|
public async Task HeadResponseCanContainContentLengthHeader()
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger();
|
|
||||||
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
|
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
httpContext.Response.ContentLength = 42;
|
httpContext.Response.ContentLength = 42;
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
}, serviceContext))
|
}, new TestServiceContext()))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
|
|
@ -746,7 +774,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
$"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
"Content-Length: 42",
|
"Content-Length: 42",
|
||||||
"",
|
"",
|
||||||
|
|
@ -758,14 +786,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HeadResponseCanContainContentLengthHeaderButBodyNotWritten()
|
public async Task HeadResponseCanContainContentLengthHeaderButBodyNotWritten()
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger();
|
|
||||||
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
|
|
||||||
|
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
httpContext.Response.ContentLength = 12;
|
httpContext.Response.ContentLength = 12;
|
||||||
await httpContext.Response.WriteAsync("hello, world");
|
await httpContext.Response.WriteAsync("hello, world");
|
||||||
}, serviceContext))
|
}, new TestServiceContext()))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
|
|
@ -774,7 +799,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
$"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
"Content-Length: 12",
|
"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
|
public static TheoryData<string, StringValues, string> NullHeaderData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue