diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs index d0e90e105..07b6a2921 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs @@ -46,7 +46,7 @@ public abstract partial class HttpProtocol : IHttpResponseControl private CancellationToken? _manuallySetRequestAbortToken; protected RequestProcessingStatus _requestProcessingStatus; - protected volatile bool _keepAlive = true; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx + protected volatile bool _keepAlive; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx protected bool _upgradeAvailable; private bool _canHaveBody; private bool _autoChunk; @@ -440,139 +440,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl { try { - while (_keepAlive) - { - BeginRequestProcessing(); - - var result = default(ReadResult); - var endConnection = false; - do - { - if (BeginRead(out var awaitable)) - { - result = await awaitable; - } - } while (!TryParseRequest(result, out endConnection)); - - if (endConnection) - { - return; - } - - var messageBody = CreateMessageBody(); - - if (!messageBody.RequestKeepAlive) - { - _keepAlive = false; - } - - _upgradeAvailable = messageBody.RequestUpgrade; - - InitializeStreams(messageBody); - - var httpContext = application.CreateContext(this); - - try - { - try - { - KestrelEventSource.Log.RequestStart(this); - - await application.ProcessRequestAsync(httpContext); - - if (_requestAborted == 0) - { - VerifyResponseContentLength(); - } - } - catch (Exception ex) - { - ReportApplicationError(ex); - - if (ex is BadHttpRequestException) - { - throw; - } - } - finally - { - KestrelEventSource.Log.RequestStop(this); - - // Trigger OnStarting if it hasn't been called yet and the app hasn't - // already failed. If an OnStarting callback throws we can go through - // our normal error handling in ProduceEnd. - // https://github.com/aspnet/KestrelHttpServer/issues/43 - if (!HasResponseStarted && _applicationException == null && _onStarting != null) - { - await FireOnStarting(); - } - - PauseStreams(); - - if (_onCompleted != null) - { - await FireOnCompleted(); - } - } - - // If _requestAbort is set, the connection has already been closed. - if (_requestAborted == 0) - { - // Call ProduceEnd() before consuming the rest of the request body to prevent - // delaying clients waiting for the chunk terminator: - // - // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663 - // - // This also prevents the 100 Continue response from being sent if the app - // never tried to read the body. - // https://github.com/aspnet/KestrelHttpServer/issues/2102 - // - // ProduceEnd() must be called before _application.DisposeContext(), to ensure - // HttpContext.Response.StatusCode is correctly set when - // IHttpContextFactory.Dispose(HttpContext) is called. - await ProduceEnd(); - - // ForZeroContentLength does not complete the reader nor the writer - if (!messageBody.IsEmpty) - { - // Finish reading the request body in case the app did not. - await messageBody.ConsumeAsync(); - } - } - 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 - { - application.DisposeContext(httpContext, _applicationException); - - // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block - // to ensure InitializeStreams has been called. - StopStreams(); - - if (HasStartedConsumingRequestBody) - { - RequestBodyPipe.Reader.Complete(); - - // Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete(). - await messageBody.StopAsync(); - - // At this point both the request body pipe reader and writer should be completed. - RequestBodyPipe.Reset(); - } - } - } + await ProcessRequests(application); } catch (BadHttpRequestException ex) { @@ -615,6 +483,156 @@ public async Task ProcessRequestsAsync(IHttpApplication appl } } + private async Task ProcessRequests(IHttpApplication application) + { + // Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value + _keepAlive = true; + do + { + BeginRequestProcessing(); + + var result = default(ReadResult); + var endConnection = false; + do + { + if (BeginRead(out var awaitable)) + { + result = await awaitable; + } + } while (!TryParseRequest(result, out endConnection)); + + if (endConnection) + { + // Connection finished, stop processing requests + return; + } + + var messageBody = CreateMessageBody(); + if (!messageBody.RequestKeepAlive) + { + _keepAlive = false; + } + + _upgradeAvailable = messageBody.RequestUpgrade; + + InitializeStreams(messageBody); + + var httpContext = application.CreateContext(this); + + BadHttpRequestException badRequestException = null; + try + { + KestrelEventSource.Log.RequestStart(this); + + // Run the application code for this request + await application.ProcessRequestAsync(httpContext); + + if (_requestAborted == 0) + { + VerifyResponseContentLength(); + } + } + catch (Exception ex) + { + ReportApplicationError(ex); + // Capture BadHttpRequestException for further processing + badRequestException = ex as BadHttpRequestException; + } + + KestrelEventSource.Log.RequestStop(this); + + // Trigger OnStarting if it hasn't been called yet and the app hasn't + // already failed. If an OnStarting callback throws we can go through + // our normal error handling in ProduceEnd. + // https://github.com/aspnet/KestrelHttpServer/issues/43 + if (!HasResponseStarted && _applicationException == null && _onStarting != null) + { + await FireOnStarting(); + } + + PauseStreams(); + + if (_onCompleted != null) + { + await FireOnCompleted(); + } + + if (badRequestException == null) + { + // If _requestAbort is set, the connection has already been closed. + if (_requestAborted == 0) + { + // Call ProduceEnd() before consuming the rest of the request body to prevent + // delaying clients waiting for the chunk terminator: + // + // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663 + // + // This also prevents the 100 Continue response from being sent if the app + // never tried to read the body. + // https://github.com/aspnet/KestrelHttpServer/issues/2102 + // + // ProduceEnd() must be called before _application.DisposeContext(), to ensure + // HttpContext.Response.StatusCode is correctly set when + // IHttpContextFactory.Dispose(HttpContext) is called. + await ProduceEnd(); + + // ForZeroContentLength does not complete the reader nor the writer + if (!messageBody.IsEmpty) + { + try + { + // Finish reading the request body in case the app did not. + await messageBody.ConsumeAsync(); + } + catch (BadHttpRequestException ex) + { + // Capture BadHttpRequestException for further processing + badRequestException = ex; + } + } + } + else if (!HasResponseStarted) + { + // If the request was aborted and no response was sent, there's no + // meaningful status code to log. + StatusCode = 0; + } + } + + if (badRequestException != null) + { + // 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(badRequestException); + } + + application.DisposeContext(httpContext, _applicationException); + + // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block + // to ensure InitializeStreams has been called. + StopStreams(); + + if (HasStartedConsumingRequestBody) + { + RequestBodyPipe.Reader.Complete(); + + // Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete(). + await messageBody.StopAsync(); + + // At this point both the request body pipe reader and writer should be completed. + RequestBodyPipe.Reset(); + } + + if (badRequestException != null) + { + // Bad request reported, stop processing requests + return; + } + + } while (_keepAlive); + } + public void OnStarting(Func callback, object state) { lock (_onStartingSync)