/// <summary> /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the /// application delegate for as long as the socket is intended to remain open. /// The resulting Task from this loop is preserved in a field which is used when the server needs /// to drain and close all currently active connections. /// </summary> public override async Task RequestProcessingAsync() { var requestLineStatus = RequestLineStatus.Empty; try { while (!_requestProcessingStopping) { ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection); while (!_requestProcessingStopping) { requestLineStatus = TakeStartLine(SocketInput); if (requestLineStatus == RequestLineStatus.Done) { break; } if (SocketInput.CheckFinOrThrow()) { // We need to attempt to consume start lines and headers even after // SocketInput.RemoteIntakeFin is set to true to ensure we don't close a // connection without giving the application a chance to respond to a request // sent immediately before the a FIN from the client. requestLineStatus = TakeStartLine(SocketInput); if (requestLineStatus == RequestLineStatus.Empty) { return; } if (requestLineStatus != RequestLineStatus.Done) { RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString()); } break; } await SocketInput; } InitializeHeaders(); while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, FrameRequestHeaders)) { if (SocketInput.CheckFinOrThrow()) { // We need to attempt to consume start lines and headers even after // SocketInput.RemoteIntakeFin is set to true to ensure we don't close a // connection without giving the application a chance to respond to a request // sent immediately before the a FIN from the client. if (!TakeMessageHeaders(SocketInput, FrameRequestHeaders)) { RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders); } break; } await SocketInput; } if (!_requestProcessingStopping) { var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; _upgrade = messageBody.RequestUpgrade; InitializeStreams(messageBody); var context = _application.CreateContext(this); try { try { await _application.ProcessRequestAsync(context).ConfigureAwait(false); if (Volatile.Read(ref _requestAborted) == 0) { VerifyResponseContentLength(); } } catch (Exception ex) { ReportApplicationError(ex); if (ex is BadHttpRequestException) { throw; } } finally { // 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 (Volatile.Read(ref _requestAborted) == 0) { ResumeStreams(); if (HasResponseStarted) { // If the response has already started, 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 // // ProduceEnd() must be called before _application.DisposeContext(), to ensure // HttpContext.Response.StatusCode is correctly set when // IHttpContextFactory.Dispose(HttpContext) is called. await ProduceEnd(); } if (_keepAlive) { // Finish reading the request body in case the app did not. await messageBody.Consume(); } if (!HasResponseStarted) { 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 { _application.DisposeContext(context, _applicationException); // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block // to ensure InitializeStreams has been called. StopStreams(); } } if (!_keepAlive) { // End the connection for non keep alive as data incoming may have been thrown off return; } // Don't reset frame state if we're exiting the loop. This avoids losing request rejection // information (for 4xx response), and prevents ObjectDisposedException on HTTPS (ODEs // will be thrown if PrepareRequest is not null and references objects disposed on connection // close - see https://github.com/aspnet/KestrelHttpServer/issues/1103#issuecomment-250237677). if (!_requestProcessingStopping) { Reset(); } } } catch (BadHttpRequestException ex) { // Handle BadHttpRequestException thrown during request line or header parsing. // SetBadRequestState logs the error. SetBadRequestState(ex); } catch (IOException ex) when(ex.InnerException is UvException) { // Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly. if (requestLineStatus != RequestLineStatus.Empty || ((UvException)ex.InnerException).StatusCode != Constants.ECONNRESET) { Log.RequestProcessingError(ConnectionId, ex); } } catch (Exception ex) { Log.LogWarning(0, ex, "Connection processing ended abnormally"); } finally { try { // If _requestAborted is set, the connection has already been closed. if (Volatile.Read(ref _requestAborted) == 0) { await TryProduceInvalidRequestResponse(); ConnectionControl.End(ProduceEndType.SocketShutdown); } } catch (Exception ex) { Log.LogWarning(0, ex, "Connection shutdown abnormally"); } } }
/// <summary> /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the /// application delegate for as long as the socket is intended to remain open. /// The resulting Task from this loop is preserved in a field which is used when the server needs /// to drain and close all currently active connections. /// </summary> public override async Task RequestProcessingAsync() { try { while (!_requestProcessingStopping) { while (!_requestProcessingStopping && TakeStartLine(SocketInput) != RequestLineStatus.Done) { if (SocketInput.RemoteIntakeFin) { // We need to attempt to consume start lines and headers even after // SocketInput.RemoteIntakeFin is set to true to ensure we don't close a // connection without giving the application a chance to respond to a request // sent immediately before the a FIN from the client. var requestLineStatus = TakeStartLine(SocketInput); if (requestLineStatus == RequestLineStatus.Empty) { return; } if (requestLineStatus != RequestLineStatus.Done) { RejectRequest($"Malformed request: {requestLineStatus}"); } break; } await SocketInput; } InitializeHeaders(); while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, FrameRequestHeaders)) { if (SocketInput.RemoteIntakeFin) { // We need to attempt to consume start lines and headers even after // SocketInput.RemoteIntakeFin is set to true to ensure we don't close a // connection without giving the application a chance to respond to a request // sent immediately before the a FIN from the client. if (!TakeMessageHeaders(SocketInput, FrameRequestHeaders)) { RejectRequest($"Malformed request: invalid headers."); } break; } await SocketInput; } if (!_requestProcessingStopping) { var messageBody = MessageBody.For(HttpVersion, FrameRequestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; InitializeStreams(messageBody); var context = _application.CreateContext(this); try { await _application.ProcessRequestAsync(context).ConfigureAwait(false); } catch (Exception ex) { ReportApplicationError(ex); } finally { // 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(); } _application.DisposeContext(context, _applicationException); } // 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(); } await ProduceEnd(); } StopStreams(); if (!_keepAlive) { // End the connection for non keep alive as data incoming may have been thrown off return; } } Reset(); } } catch (BadHttpRequestException ex) { if (!_requestRejected) { SetBadRequestState(ex); Log.LogWarning(0, ex, "Connection processing ended abnormally"); } } catch (Exception ex) { Log.LogWarning(0, ex, "Connection processing ended abnormally"); } finally { try { await TryProduceInvalidRequestResponse(); // If _requestAborted is set, the connection has already been closed. if (Volatile.Read(ref _requestAborted) == 0) { ConnectionControl.End(ProduceEndType.SocketShutdown); } } catch (Exception ex) { Log.LogWarning(0, ex, "Connection shutdown abnormally"); } } }