예제 #1
0
        /// <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");
                }
            }
        }
예제 #2
0
        /// <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");
                }
            }
        }