File: Internal\Http\Http1Connection.cs
Web Access
Project: src\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj (Microsoft.AspNetCore.Server.Kestrel.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
 
internal partial class Http1Connection : HttpProtocol, IRequestProcessor, IHttpOutputAborter
{
    internal static ReadOnlySpan<byte> Http2GoAwayHttp11RequiredBytes => [0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13];
 
    private const byte ByteCR = (byte)'\r';
    private const byte ByteLF = (byte)'\n';
    private const byte ByteAsterisk = (byte)'*';
    private const byte ByteForwardSlash = (byte)'/';
    private const string Asterisk = "*";
    private const string ForwardSlash = "/";
 
    private readonly HttpConnectionContext _context;
    private readonly IHttpParser<Http1ParsingHandler> _parser;
    private readonly Http1OutputProducer _http1Output;
 
    private volatile bool _requestTimedOut;
    private uint _requestCount;
 
    private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown;
    private Uri? _absoluteRequestTarget;
 
    // The _parsed fields cache the Path, QueryString, RawTarget, and/or _absoluteRequestTarget
    // from the previous request when DisableStringReuse is false.
    private string? _parsedPath;
    private string? _parsedQueryString;
    private string? _parsedRawTarget;
    private Uri? _parsedAbsoluteRequestTarget;
 
    private long _remainingRequestHeadersBytesAllowed;
 
    // Tracks whether a HTTP/2 preface was detected during the first request.
    private bool _http2PrefaceDetected;
 
    public Http1Connection(HttpConnectionContext context)
    {
        Initialize(context);
 
        _context = context;
        _parser = ServiceContext.HttpParser;
 
        _http1Output = new Http1OutputProducer(
            _context.Transport.Output,
            _context.ConnectionId,
            _context.ConnectionContext,
            _context.MemoryPool,
            _context.ServiceContext.Log,
            _context.TimeoutControl,
            minResponseDataRateFeature: this,
            MetricsContext,
            outputAborter: this);
 
        Input = _context.Transport.Input;
        Output = _http1Output;
        MemoryPool = _context.MemoryPool;
    }
 
    public ConnectionMetricsContext MetricsContext => _context.MetricsContext;
 
    public PipeReader Input { get; }
 
    public bool RequestTimedOut => _requestTimedOut;
 
    public MinDataRate? MinResponseDataRate { get; set; }
 
    public MemoryPool<byte> MemoryPool { get; }
 
    protected override void OnRequestProcessingEnded()
    {
        if (IsUpgraded)
        {
            KestrelEventSource.Log.RequestUpgradedStop(this);
            ServiceContext.Metrics.RequestUpgradedStop(MetricsContext);
 
            ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne();
        }
 
        TimeoutControl.StartDrainTimeout(MinResponseDataRate, ServerOptions.Limits.MaxResponseBufferSize);
 
        // Prevent RequestAborted from firing. Free up unneeded feature references.
        Reset();
 
        _http1Output.Dispose();
    }
 
    void IRequestProcessor.OnInputOrOutputCompleted()
    {
        // Closed gracefully.
        _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionEndReason.TransportCompleted);
        CancelRequestAbortedToken();
    }
 
    void IHttpOutputAborter.OnInputOrOutputCompleted()
    {
        _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), ConnectionEndReason.TransportCompleted);
        CancelRequestAbortedToken();
    }
 
    /// <summary>
    /// Immediately kill the connection and poison the request body stream with an error.
    /// </summary>
    public void Abort(ConnectionAbortedException abortReason, ConnectionEndReason reason)
    {
        _http1Output.Abort(abortReason, reason);
        CancelRequestAbortedToken();
        PoisonBody(abortReason);
    }
 
    protected override void ApplicationAbort()
    {
        Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier);
        Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), ConnectionEndReason.AbortedByApp);
    }
 
    /// <summary>
    /// Stops the request processing loop between requests.
    /// Called on all active connections when the server wants to initiate a shutdown
    /// and after a keep-alive timeout.
    /// </summary>
    public void StopProcessingNextRequest(ConnectionEndReason reason)
    {
        DisableKeepAlive(reason);
        Input.CancelPendingRead();
    }
 
    internal override void DisableKeepAlive(ConnectionEndReason reason)
    {
        KestrelMetrics.AddConnectionEndReason(MetricsContext, reason);
        _keepAlive = false;
    }
 
    public void SendTimeoutResponse()
    {
        _requestTimedOut = true;
        Input.CancelPendingRead();
    }
 
    public void HandleRequestHeadersTimeout()
    {
        KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.RequestHeadersTimeout);
        SendTimeoutResponse();
    }
 
    public void HandleReadDataRateTimeout()
    {
        Debug.Assert(MinRequestBodyDataRate != null);
 
        KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.MinRequestBodyDataRate);
        Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, TraceIdentifier, MinRequestBodyDataRate.BytesPerSecond);
        SendTimeoutResponse();
    }
 
    public bool ParseRequest(ref SequenceReader<byte> reader)
    {
        switch (_requestProcessingStatus)
        {
            case RequestProcessingStatus.RequestPending:
                // Skip any empty lines (\r or \n) between requests.
                // Peek first as a minor performance optimization; it's a quick inlined check.
                if (reader.TryPeek(out byte b) && (b == ByteCR || b == ByteLF))
                {
                    reader.AdvancePastAny(ByteCR, ByteLF);
                }
 
                if (reader.End)
                {
                    break;
                }
 
                TimeoutControl.ResetTimeout(ServerOptions.Limits.RequestHeadersTimeout, TimeoutReason.RequestHeaders);
 
                _requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
                goto case RequestProcessingStatus.ParsingRequestLine;
            case RequestProcessingStatus.ParsingRequestLine:
                if (TakeStartLine(ref reader))
                {
                    _requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
                    goto case RequestProcessingStatus.ParsingHeaders;
                }
                else
                {
                    break;
                }
            case RequestProcessingStatus.ParsingHeaders:
                if (TakeMessageHeaders(ref reader, trailers: false))
                {
                    _requestProcessingStatus = RequestProcessingStatus.AppStarted;
                    // Consumed preamble
                    return true;
                }
                break;
        }
 
        // Haven't completed consuming preamble
        return false;
    }
 
    public bool TakeStartLine(ref SequenceReader<byte> reader)
    {
        // Make sure the buffer is limited
        if (reader.Remaining >= ServerOptions.Limits.MaxRequestLineSize)
        {
            // Input oversize, cap amount checked
            return TrimAndTakeStartLine(ref reader);
        }
 
        return _parser.ParseRequestLine(new Http1ParsingHandler(this), ref reader);
 
        bool TrimAndTakeStartLine(ref SequenceReader<byte> reader)
        {
            var trimmedBuffer = reader.Sequence.Slice(reader.Position, ServerOptions.Limits.MaxRequestLineSize);
            var trimmedReader = new SequenceReader<byte>(trimmedBuffer);
 
            if (!_parser.ParseRequestLine(new Http1ParsingHandler(this), ref trimmedReader))
            {
                // We read the maximum allowed but didn't complete the start line.
                KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
            }
 
            reader.Advance(trimmedReader.Consumed);
            return true;
        }
    }
 
    public bool TakeMessageHeaders(ref SequenceReader<byte> reader, bool trailers)
    {
        // Make sure the buffer is limited
        if (reader.Remaining > _remainingRequestHeadersBytesAllowed)
        {
            // Input oversize, cap amount checked
            return TrimAndTakeMessageHeaders(ref reader, trailers);
        }
 
        var alreadyConsumed = reader.Consumed;
 
        try
        {
            var result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader);
            if (result)
            {
                TimeoutControl.CancelTimeout();
            }
 
            return result;
        }
        finally
        {
            _remainingRequestHeadersBytesAllowed -= reader.Consumed - alreadyConsumed;
        }
 
        bool TrimAndTakeMessageHeaders(ref SequenceReader<byte> reader, bool trailers)
        {
            var trimmedBuffer = reader.Sequence.Slice(reader.Position, _remainingRequestHeadersBytesAllowed);
            var trimmedReader = new SequenceReader<byte>(trimmedBuffer);
            try
            {
                if (!_parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref trimmedReader))
                {
                    // We read the maximum allowed but didn't complete the headers.
                    KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
                }
 
                TimeoutControl.CancelTimeout();
 
                reader.Advance(trimmedReader.Consumed);
 
                return true;
            }
            finally
            {
                _remainingRequestHeadersBytesAllowed -= trimmedReader.Consumed;
            }
        }
    }
 
    public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
    {
        // Null characters are not allowed and should have been checked by HttpParser before calling this method
        Debug.Assert(startLine.IndexOf((byte)0) == -1);
 
        var targetStart = targetPath.Offset;
        // Slice out target
        var target = startLine[targetStart..];
        Debug.Assert(target.Length != 0, "Request target must be non-zero length");
        var method = versionAndMethod.Method;
        var ch = target[0];
        if (ch == ByteForwardSlash)
        {
            // origin-form.
            // The most common form of request-target.
            // https://tools.ietf.org/html/rfc7230#section-5.3.1
            OnOriginFormTarget(targetPath, target);
        }
        else if (ch == ByteAsterisk && target.Length == 1)
        {
            OnAsteriskFormTarget(method);
        }
        else if (startLine[targetStart..].GetKnownHttpScheme(out _))
        {
            OnAbsoluteFormTarget(targetPath, target);
        }
        else
        {
            // Assume anything else is considered authority form.
            // FYI: this should be an edge case. This should only happen when
            // a client mistakenly thinks this server is a proxy server.
            OnAuthorityFormTarget(method, target);
        }
 
        Method = method;
        if (method == HttpMethod.Custom)
        {
            _methodText = startLine[..versionAndMethod.MethodEnd].GetAsciiString();
        }
 
        _httpVersion = versionAndMethod.Version;
 
        Debug.Assert(RawTarget != null, "RawTarget was not set");
        Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set");
        Debug.Assert(Path != null, "Path was not set");
        Debug.Assert(QueryString != null, "QueryString was not set");
        Debug.Assert(HttpVersion != null, "HttpVersion was not set");
    }
 
    // Compare with Http2Stream.TryValidatePseudoHeaders
    private void OnOriginFormTarget(TargetOffsetPathLength targetPath, Span<byte> target)
    {
        Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");
 
        _requestTargetForm = HttpRequestTarget.OriginForm;
 
        if (target.Length == 1)
        {
            // If target.Length == 1 it can only be a forward slash (e.g. home page)
            // and we know RawTarget and Path are the same and QueryString is Empty
            RawTarget = ForwardSlash;
            Path = ForwardSlash;
            QueryString = string.Empty;
            // Clear parsedData as we won't check it if we come via this path again,
            // an setting to null is fast as it doesn't need to use a GC write barrier.
            _parsedRawTarget = _parsedPath = _parsedQueryString = null;
            _parsedAbsoluteRequestTarget = null;
            return;
        }
 
        // Read raw target before mutating memory.
        var previousValue = _parsedRawTarget;
        if (ServerOptions.DisableStringReuse ||
            previousValue == null || previousValue.Length != target.Length ||
            !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
        {
            ParseTarget(targetPath, target);
        }
        else
        {
            // As RawTarget is the same we can reuse the previous parsed values.
            RawTarget = previousValue;
            Path = _parsedPath;
            QueryString = _parsedQueryString;
        }
 
        // Clear parsedData for absolute target as we won't check it if we come via this path again,
        // an setting to null is fast as it doesn't need to use a GC write barrier.
        _parsedAbsoluteRequestTarget = null;
    }
 
    private void ParseTarget(TargetOffsetPathLength targetPath, Span<byte> target)
    {
        // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
        // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
        // then encoded/escaped to ASCII  https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
 
        try
        {
            // The previous string does not match what the bytes would convert to,
            // so we will need to generate a new string.
            RawTarget = _parsedRawTarget = target.GetAsciiString();
 
            var queryLength = 0;
            if (target.Length == targetPath.Length)
            {
                // No query string
                if (ReferenceEquals(_parsedQueryString, string.Empty))
                {
                    QueryString = _parsedQueryString;
                }
                else
                {
                    QueryString = string.Empty;
                    _parsedQueryString = string.Empty;
                }
            }
            else
            {
                queryLength = ParseQuery(targetPath, target);
            }
 
            var pathLength = targetPath.Length;
            if (pathLength == 1)
            {
                // If path.Length == 1 it can only be a forward slash (e.g. home page)
                Path = _parsedPath = ForwardSlash;
            }
            else
            {
                var path = target[..pathLength];
                Path = _parsedPath = PathDecoder.DecodePath(path, targetPath.IsEncoded, RawTarget, queryLength);
            }
        }
        catch (InvalidOperationException)
        {
            ThrowRequestTargetRejected(target);
        }
    }
 
    private int ParseQuery(TargetOffsetPathLength targetPath, Span<byte> target)
    {
        var previousValue = _parsedQueryString;
        var query = target[targetPath.Length..];
        var queryLength = query.Length;
        if (ServerOptions.DisableStringReuse ||
            previousValue == null || previousValue.Length != queryLength ||
            !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
        {
            // The previous string does not match what the bytes would convert to,
            // so we will need to generate a new string.
            QueryString = _parsedQueryString = query.GetAsciiString();
        }
        else
        {
            // Same as previous
            QueryString = _parsedQueryString;
        }
 
        return queryLength;
    }
 
    private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target)
    {
        _requestTargetForm = HttpRequestTarget.AuthorityForm;
 
        // This is not complete validation. It is just a quick scan for invalid characters
        // but doesn't check that the target fully matches the URI spec.
        if (HttpCharacters.ContainsInvalidAuthorityChar(target))
        {
            ThrowRequestTargetRejected(target);
        }
 
        // The authority-form of request-target is only used for CONNECT
        // requests (https://tools.ietf.org/html/rfc9110#section-9.3.6).
        if (method != HttpMethod.Connect)
        {
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.ConnectMethodRequired);
        }
 
        // When making a CONNECT request to establish a tunnel through one or
        // more proxies, a client MUST send only the target URI's authority
        // component (excluding any userinfo and its "@" delimiter) as the
        // request-target.For example,
        //
        //  CONNECT www.example.com:80 HTTP/1.1
        //
        // Allowed characters in the 'host + port' section of authority.
        // See https://tools.ietf.org/html/rfc3986#section-3.2
 
        var previousValue = _parsedRawTarget;
        if (ServerOptions.DisableStringReuse ||
            previousValue == null || previousValue.Length != target.Length ||
            !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
        {
            // The previous string does not match what the bytes would convert to,
            // so we will need to generate a new string.
            RawTarget = _parsedRawTarget = target.GetAsciiString();
        }
        else
        {
            // Reuse previous value
            RawTarget = _parsedRawTarget;
        }
 
        Path = string.Empty;
        QueryString = string.Empty;
        // Clear parsedData for path, queryString and absolute target as we won't check it if we come via this path again,
        // an setting to null is fast as it doesn't need to use a GC write barrier.
        _parsedPath = _parsedQueryString = null;
        _parsedAbsoluteRequestTarget = null;
    }
 
    private void OnAsteriskFormTarget(HttpMethod method)
    {
        _requestTargetForm = HttpRequestTarget.AsteriskForm;
 
        // The asterisk-form of request-target is only used for a server-wide
        // OPTIONS request (https://tools.ietf.org/html/rfc9110#section-9.3.7).
        if (method != HttpMethod.Options)
        {
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.OptionsMethodRequired);
        }
 
        RawTarget = Asterisk;
        Path = string.Empty;
        QueryString = string.Empty;
        // Clear parsedData as we won't check it if we come via this path again,
        // an setting to null is fast as it doesn't need to use a GC write barrier.
        _parsedRawTarget = _parsedPath = _parsedQueryString = null;
        _parsedAbsoluteRequestTarget = null;
    }
 
    private void OnAbsoluteFormTarget(TargetOffsetPathLength targetPath, Span<byte> target)
    {
        Span<byte> query = target[targetPath.Length..];
        _requestTargetForm = HttpRequestTarget.AbsoluteForm;
 
        // absolute-form
        // https://tools.ietf.org/html/rfc7230#section-5.3.2
 
        // This code should be the edge-case.
 
        // From the spec:
        //    a server MUST accept the absolute-form in requests, even though
        //    HTTP/1.1 clients will only send them in requests to proxies.
 
        var disableStringReuse = ServerOptions.DisableStringReuse;
        var previousValue = _parsedRawTarget;
        if (disableStringReuse ||
            previousValue == null || previousValue.Length != target.Length ||
            !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
        {
            try
            {
                // The previous string does not match what the bytes would convert to,
                // so we will need to generate a new string.
                RawTarget = _parsedRawTarget = target.GetAsciiString();
            }
            catch (InvalidOperationException)
            {
                // GetAsciiStringNonNullCharacters throws an InvalidOperationException if there are
                // invalid characters in the string. This is hard to understand/diagnose, so let's
                // catch it and instead throw a more meaningful error. This matches the behavior in
                // the origin-form case.
                ThrowRequestTargetRejected(target);
            }
 
            // Validation of absolute URIs is slow, but clients
            // should not be sending this form anyways, so perf optimization
            // not high priority
 
            if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
            {
                ThrowRequestTargetRejected(target);
            }
 
            _absoluteRequestTarget = _parsedAbsoluteRequestTarget = uri;
            Path = _parsedPath = uri.LocalPath;
            // don't use uri.Query because we need the unescaped version
            previousValue = _parsedQueryString;
            if (disableStringReuse ||
                previousValue == null || previousValue.Length != query.Length ||
                !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
            {
                // The previous string does not match what the bytes would convert to,
                // so we will need to generate a new string.
                QueryString = _parsedQueryString = query.GetAsciiString();
            }
            else
            {
                QueryString = _parsedQueryString;
            }
        }
        else
        {
            // As RawTarget is the same we can reuse the previous values.
            RawTarget = _parsedRawTarget;
            Path = _parsedPath;
            QueryString = _parsedQueryString;
            _absoluteRequestTarget = _parsedAbsoluteRequestTarget;
        }
    }
 
    internal void EnsureHostHeaderExists()
    {
        // https://tools.ietf.org/html/rfc7230#section-5.4
        // A server MUST respond with a 400 (Bad Request) status code to any
        // HTTP/1.1 request message that lacks a Host header field and to any
        // request message that contains more than one Host header field or a
        // Host header field with an invalid field-value.
 
        var hostCount = HttpRequestHeaders.HostCount;
        var hostText = HttpRequestHeaders.HeaderHost.ToString();
        if (hostCount <= 0)
        {
            if (_httpVersion == Http.HttpVersion.Http10)
            {
                return;
            }
 
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
        }
        else if (hostCount > 1)
        {
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
        }
        else if (_requestTargetForm != HttpRequestTarget.OriginForm)
        {
            // Tail call
            ValidateNonOriginHostHeader(hostText);
        }
        else if (!HttpUtilities.IsHostHeaderValid(hostText))
        {
            KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders);
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
        }
    }
 
    private void ValidateNonOriginHostHeader(string hostText)
    {
        if (_requestTargetForm == HttpRequestTarget.AuthorityForm)
        {
            if (hostText != RawTarget)
            {
                KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders);
                KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
            }
        }
        else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm)
        {
            // If the target URI includes an authority component, then a
            // client MUST send a field - value for Host that is identical to that
            // authority component, excluding any userinfo subcomponent and its "@"
            // delimiter.
 
            // Accessing authority always allocates, store it in a local to only allocate once
            var authority = _absoluteRequestTarget!.Authority;
 
            // System.Uri doesn't not tell us if the port was in the original string or not.
            // When IsDefaultPort = true, we will allow Host: with or without the default port
            if (hostText != authority)
            {
                if (!_absoluteRequestTarget.IsDefaultPort
                    || hostText != $"{authority}:{_absoluteRequestTarget.Port}")
                {
                    if (_context.ServiceContext.ServerOptions.AllowHostHeaderOverride)
                    {
                        // No need to include the port here, it's either already in the Authority
                        // or it's the default port
                        // see: https://datatracker.ietf.org/doc/html/rfc2616/#section-14.23
                        // A "host" without any trailing port information implies the default
                        // port for the service requested (e.g., "80" for an HTTP URL).
                        hostText = authority;
                        HttpRequestHeaders.HeaderHost = hostText;
                    }
                    else
                    {
                        KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders);
                        KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
                    }
                }
            }
        }
 
        if (!HttpUtilities.IsHostHeaderValid(hostText))
        {
            KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders);
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
        }
    }
 
    protected override void OnReset()
    {
        _requestTimedOut = false;
        _requestTargetForm = HttpRequestTarget.Unknown;
        _absoluteRequestTarget = null;
        _remainingRequestHeadersBytesAllowed = (long)ServerOptions.Limits.MaxRequestHeadersTotalSize + 2;
 
        MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate;
 
        // Reset Http1 Features
        _currentIHttpMinRequestBodyDataRateFeature = this;
        _currentIHttpMinResponseDataRateFeature = this;
        _currentIPersistentStateFeature = this;
    }
 
    protected override void OnRequestProcessingEnding()
    {
    }
 
    protected override string CreateRequestId()
        => StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', _requestCount);
 
    protected override MessageBody CreateMessageBody()
        => Http1MessageBody.For(_httpVersion, HttpRequestHeaders, this);
 
    protected override void BeginRequestProcessing()
    {
        // Reset the features and timeout.
        Reset();
        _requestCount++;
        TimeoutControl.SetTimeout(ServerOptions.Limits.KeepAliveTimeout, TimeoutReason.KeepAlive);
    }
 
    protected override bool BeginRead(out ValueTask<ReadResult> awaitable)
    {
        awaitable = Input.ReadAsync();
        return true;
    }
 
    protected override bool TryParseRequest(ReadResult result, out bool endConnection)
    {
        var reader = new SequenceReader<byte>(result.Buffer);
        var isConsumed = false;
        try
        {
            isConsumed = ParseRequest(ref reader);
        }
        catch (InvalidOperationException) when (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
        {
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
            throw;
        }
#pragma warning disable CS0618 // Type or member is obsolete
        catch (BadHttpRequestException ex)
        {
            OnBadRequest(result.Buffer, ex);
            throw;
        }
#pragma warning restore CS0618 // Type or member is obsolete
        catch (Exception)
        {
            KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.OtherError);
            throw;
        }
        finally
        {
            Input.AdvanceTo(reader.Position, isConsumed ? reader.Position : result.Buffer.End);
        }
 
        if (result.IsCompleted)
        {
            switch (_requestProcessingStatus)
            {
                case RequestProcessingStatus.RequestPending:
                    endConnection = true;
                    return true;
                case RequestProcessingStatus.ParsingRequestLine:
                    KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
                    break;
                case RequestProcessingStatus.ParsingHeaders:
                    KestrelBadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
                    break;
            }
        }
        else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending)
        {
            // Stop the request processing loop if the server is shutting down or there was a keep-alive timeout
            // and there is no ongoing request.
            endConnection = true;
            return true;
        }
        else if (RequestTimedOut)
        {
            // In this case, there is an ongoing request but the start line/header parsing has timed out, so send
            // a 408 response.
            KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestHeadersTimeout);
        }
 
        endConnection = false;
        if (_requestProcessingStatus == RequestProcessingStatus.AppStarted)
        {
            EnsureHostHeaderExists();
            return true;
        }
        else
        {
            return false;
        }
    }
 
    internal static ConnectionEndReason GetConnectionEndReason(Microsoft.AspNetCore.Http.BadHttpRequestException ex)
    {
#pragma warning disable CS0618 // Type or member is obsolete
        var kestrelEx = ex as BadHttpRequestException;
#pragma warning restore CS0618 // Type or member is obsolete
 
        switch (kestrelEx?.Reason)
        {
            case RequestRejectionReason.UnrecognizedHTTPVersion:
                return ConnectionEndReason.InvalidHttpVersion;
            case RequestRejectionReason.InvalidRequestLine:
            case RequestRejectionReason.RequestLineTooLong:
            case RequestRejectionReason.InvalidRequestTarget:
                return ConnectionEndReason.InvalidRequestLine;
            case RequestRejectionReason.InvalidRequestHeadersNoCRLF:
            case RequestRejectionReason.InvalidRequestHeader:
            case RequestRejectionReason.InvalidContentLength:
            case RequestRejectionReason.MultipleContentLengths:
            case RequestRejectionReason.MalformedRequestInvalidHeaders:
            case RequestRejectionReason.InvalidCharactersInHeaderName:
            case RequestRejectionReason.LengthRequiredHttp10:
            case RequestRejectionReason.OptionsMethodRequired:
            case RequestRejectionReason.ConnectMethodRequired:
            case RequestRejectionReason.MissingHostHeader:
            case RequestRejectionReason.MultipleHostHeaders:
            case RequestRejectionReason.InvalidHostHeader:
                return ConnectionEndReason.InvalidRequestHeaders;
            case RequestRejectionReason.HeadersExceedMaxTotalSize:
                return ConnectionEndReason.MaxRequestHeadersTotalSizeExceeded;
            case RequestRejectionReason.TooManyHeaders:
                return ConnectionEndReason.MaxRequestHeaderCountExceeded;
            case RequestRejectionReason.TlsOverHttpError:
                return ConnectionEndReason.TlsNotSupported;
            case RequestRejectionReason.UnexpectedEndOfRequestContent:
                return ConnectionEndReason.UnexpectedEndOfRequestContent;
            default:
                // In some scenarios the end reason might already be set to a more specific error
                // and attempting to set the reason again has no impact.
                return ConnectionEndReason.OtherError;
        }
    }
 
#pragma warning disable CS0618 // Type or member is obsolete
    private void OnBadRequest(ReadOnlySequence<byte> requestData, BadHttpRequestException ex)
#pragma warning restore CS0618 // Type or member is obsolete
    {
        // Some code shared between HTTP versions throws errors. For example, HttpRequestHeaders collection
        // throws when an invalid content length is set.
        // Only want to set a reasons for HTTP/1.1 connection, so set end reason by catching the exception here.
        var reason = GetConnectionEndReason(ex);
        KestrelMetrics.AddConnectionEndReason(MetricsContext, reason);
 
        if (ex.Reason == RequestRejectionReason.UnrecognizedHTTPVersion)
        {
            DetectHttp2Preface(requestData);
        }
    }
 
    private void DetectHttp2Preface(ReadOnlySequence<byte> requestData)
    {
        const int PrefaceLineLength = 16;
 
        // Only check for HTTP/2 preface on non-TLS connection.
        // When TLS is used then ALPN is used to negotiate correct version.
        if (ConnectionFeatures.Get<ITlsHandshakeFeature>() == null)
        {
            // If there is an unrecognized HTTP version, it is the first request on the connection, and the request line
            // bytes matches the HTTP/2 preface request line bytes then log and return a HTTP/2 GOAWAY frame.
            if (_requestCount == 1
                && requestData.Length >= PrefaceLineLength)
            {
                var clientPrefaceRequestLine = Http2.Http2Connection.ClientPreface.Slice(0, PrefaceLineLength);
                var currentRequestLine = requestData.Slice(0, PrefaceLineLength).ToSpan();
                if (currentRequestLine.SequenceEqual(clientPrefaceRequestLine))
                {
                    Log.PossibleInvalidHttpVersionDetected(ConnectionId, Http.HttpVersion.Http11, Http.HttpVersion.Http2);
 
                    // Can't write GOAWAY here. Set flag so TryProduceInvalidRequestResponse writes GOAWAY.
                    _http2PrefaceDetected = true;
                }
            }
        }
    }
 
    protected override Task TryProduceInvalidRequestResponse()
    {
        if (_http2PrefaceDetected)
        {
            _context.Transport.Output.Write(Http2GoAwayHttp11RequiredBytes);
            return _context.Transport.Output.FlushAsync().GetAsTask();
        }
 
        return base.TryProduceInvalidRequestResponse();
    }
 
    void IRequestProcessor.Tick(long timestamp) { }
}