// Copyright (c) .NET Foundation.  All rights reserved. 
// Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. 

using System;
using System. Buffers;
using System. Collections. Generic;
using System. Diagnostics;
using System. IO;
using System. IO.Pipelines;
using System. Linq;
using System. Net;
using System. Runtime. CompilerServices;
using System. Text;
using System. Threading;
using System. Threading. Tasks;
using Microsoft. AspNetCore. Http;
using Microsoft. Extensions. Primitives;
using Kooboo. HttpServer;

namespace Kooboo. HttpServer. Http
{
    public abstract partial class HttpProtocol : IHttpResponseControl
    {
        private static readonly byte[] _bytesConnectionClose = Encoding. ASCII.GetBytes("\r\nConnection: close");
        private static readonly byte[] _bytesConnectionKeepAlive = Encoding. ASCII.GetBytes("\r\nConnection: keep-alive");
        private static readonly byte[] _bytesTransferEncodingChunked = Encoding. ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
        private static readonly byte[] _bytesServer = Encoding. ASCII.GetBytes("\r\nServer: " + Constants. ServerName);
        private static readonly Action<PipeWriter, ArraySegment<byte>> _writeChunk = WriteChunk;

        private readonly object _onStartingSync = new Object();
        private readonly object _onCompletedSync = new Object();

        public HttpFeatures Features
        {
            get
            {
                return _context. Features;
            }
        }

        public HttpRequestFeature HttpRequestFeature
        {
            get
            {
                return _context. Features. Request;
            }
            set
            {
                _context. Features. Request = value;
            }
        }
        public HttpResponseFeature HttpResponseFeature
        {
            get
            {
                return _context. Features. Response;
            }
            set
            {
                _context. Features. Response = value;
            }
        }

        protected Streams _streams;

        protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
        protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;

        protected volatile int _requestAborted;
        protected CancellationTokenSource _abortedCts;
        private CancellationToken? _ manuallySetRequestAbortToken;

        protected RequestProcessingStatus _requestProcessingStatus;
        protected volatile bool _keepAlive = true; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
        protected bool _upgradeAvailable;
        private bool _canHaveBody;
        private bool _autoChunk;
        protected Exception _applicationException;
        private BadHttpRequestException _requestRejectedException;

        protected HttpVersion _httpVersion;

        private string _requestId;
        protected int _requestHeadersParsed;

        protected long _responseBytesWritten;

        private readonly IHttpProtocolContext _context;

        private string _scheme = null;

        public HttpProtocol(IHttpProtocolContext context)
        {
            _context = context;

            ServerOptions = ServiceContext. ServerOptions;
            HttpResponseControl = this;
            RequestBodyPipe = CreateRequestBodyPipe();
        }

        public IHttpResponseControl HttpResponseControl { get; set; }

        public Pipe RequestBodyPipe { get; }

        public ServiceContext ServiceContext => _context. ServiceContext;
        public IPEndPoint LocalEndPoint => _context. LocalEndPoint;
        public IPEndPoint RemoteEndPoint => _context. RemoteEndPoint;

        //public IFeatureCollection ConnectionFeatures => _context. ConnectionFeatures;
        public IHttpOutputProducer Output { get; protected set; }

        protected ILogger Log => ServiceContext. Log;
        private DateHeaderValueManager DateHeaderValueManager => ServiceContext. DateHeaderValueManager;
        // Hold direct reference to ServerOptions since this is used very often in the request processing path
        protected HttpServerOptions ServerOptions { get; }
        protected string ConnectionId => _context. ConnectionId;

        public string ConnectionIdFeature { get; set; }
        public bool HasStartedConsumingRequestBody { get; set; }
        public long?  MaxRequestBodySize { get; set; }
        //bodycontrol
        public bool AllowSynchronousIO { get; set; }

        /// <summary>
        /// The request id. <seealso cref="HttpContext. TraceIdentifier"/>
        /// </summary>
        public string TraceIdentifier
        {
            set => _requestId = value;
            get
            {
                // don't generate an ID until it is requested
                if (_requestId == null)
                {
                    _requestId = CreateRequestId();
                }
                return _requestId;
            }
        }

        public abstract bool IsUpgradableRequest { get; }
        public bool IsUpgraded { get; set; }

        public IPAddress RemoteIpAddress { get; set; }
        public int RemotePort { get; set; }
        public IPAddress LocalIpAddress { get; set; }
        public int LocalPort { get; set; }
        

        public string HttpVersion
        {
            get
            {
                if (_httpVersion == Http. HttpVersion. Http11)
                {
                    return HttpUtilities. Http11Version;
                }
                if (_httpVersion == Http. HttpVersion. Http10)
                {
                    return HttpUtilities. Http10Version;
                }
                if (_httpVersion == Http. HttpVersion. Http2)
                {
                    return HttpUtilities. Http2Version;
                }

                return string. Empty;
            }

            [MethodImpl(MethodImplOptions. AggressiveInlining)]
            set
            {
                // GetKnownVersion returns versions which ReferenceEquals interned string
                // As most common path, check for this only in fast-path and inline
                if (ReferenceEquals(value, HttpUtilities. Http11Version))
                {
                    _httpVersion = Http. HttpVersion. Http11;
                }
                else if (ReferenceEquals(value, HttpUtilities. Http10Version))
                {
                    _httpVersion = Http. HttpVersion. Http10;
                }
                else if (ReferenceEquals(value, HttpUtilities. Http2Version))
                {
                    _httpVersion = Http. HttpVersion. Http2;
                }
                else
                {
                    HttpVersionSetSlow(value);
                }
            }
        }

        [MethodImpl(MethodImplOptions. NoInlining)]
        private void HttpVersionSetSlow(string value)
        {
            if (value == HttpUtilities. Http11Version)
            {
                _httpVersion = Http. HttpVersion. Http11;
            }
            else if (value == HttpUtilities. Http10Version)
            {
                _httpVersion = Http. HttpVersion. Http10;
            }
            else if (value == HttpUtilities. Http2Version)
            {
                _httpVersion = Http. HttpVersion. Http2;
            }
            else
            {
                _httpVersion = Http. HttpVersion. Unknown;
            }
        }

        public CancellationToken RequestAborted
        {
            get
            {
                // If a request abort token was previously explicitly set, return it. 
                if (_manuallySetRequestAbortToken. HasValue)
                {
                    return _manuallySetRequestAbortToken. Value;
                }
                // Otherwise, get the abort CTS.   If we have one, which would mean that someone previously
                // asked for the RequestAborted token, simply return its token.   If we don't,
                // check to see whether we've already aborted, in which case just return an
                // already canceled token.   Finally, force a source into existence if we still
                // don't have one, and return its token. 
                var cts = _abortedCts;
                return
                    cts ! = null ?  cts. Token :
                    (_requestAborted == 1) ?  new CancellationToken(true) :
                    RequestAbortedSource. Token;
            }
            set
            {
                // Set an abort token, overriding one we create internally.   This setter and associated
                // field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted. 
                _manuallySetRequestAbortToken = value;
            }
        }

        private CancellationTokenSource RequestAbortedSource
        {
            get
            {
                // Get the abort token, lazily-initializing it if necessary. 
                // Make sure it's canceled if an abort request already came in. 

                // EnsureInitialized can return null since _abortedCts is reset to null
                // after it's already been initialized to a non-null value. 
                // If EnsureInitialized does return null, this property was accessed between
                // requests so it's safe to return an ephemeral CancellationTokenSource. 
                var cts = LazyInitializer. EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource())
                            ??  new CancellationTokenSource();

                if (_requestAborted == 1)
                {
                    cts. Cancel();
                }
                return cts;
            }
        }

        public bool HasResponseStarted => _requestProcessingStatus == RequestProcessingStatus. ResponseStarted;

        protected HttpRequestHeaders HttpRequestHeaders { get; } = new HttpRequestHeaders();

        protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders();

        public MinDataRate MinRequestBodyDataRate { get; set; }

        public MinDataRate MinResponseDataRate { get; set; }


        public void InitializeStreams(MessageBody messageBody)
        {
            if (_streams == null)
            {
                _streams = new Streams(AllowSynchronousIO, httpResponseControl: this);
            }
            var pair = _streams. Start(messageBody);
            HttpRequestFeature. Body = pair. Key;
            HttpResponseFeature. Body = pair. Value;
        }

        public void PauseStreams() => _streams. Pause();

        public void StopStreams() => _streams. Stop();

        // For testing
        internal void ResetState()
        {
            _requestProcessingStatus = RequestProcessingStatus. RequestPending;
        }

        public void Reset()
        {
            _onStarting = null;
            _onCompleted = null;

            _requestProcessingStatus = RequestProcessingStatus. RequestPending;
            _autoChunk = false;
            _applicationException = null;
            _requestRejectedException = null;

            //ResetFeatureCollection();

            HasStartedConsumingRequestBody = false;
            MaxRequestBodySize = ServerOptions. Limits. MaxRequestBodySize;
            AllowSynchronousIO = ServerOptions. AllowSynchronousIO;
            TraceIdentifier = null;
            HttpRequestFeature = new HttpRequestFeature();
            HttpResponseFeature = new HttpResponseFeature();
            //Method = null;
            //PathBase = null;
            //Path = null;
            //RawTarget = null;
            //QueryString = null;
            _httpVersion = Http. HttpVersion. Unknown;
            HttpResponseFeature. StatusCode= StatusCodes. Status200OK;
            //_statusCode = StatusCodes. Status200OK;
            HttpResponseFeature. ReasonPhrase = null;

            RemoteIpAddress = RemoteEndPoint?.Address;
            RemotePort = RemoteEndPoint?.Port ??  0;

            LocalIpAddress = LocalEndPoint?.Address;
            LocalPort = LocalEndPoint?.Port ??  0;
            ConnectionIdFeature = ConnectionId;

            HttpRequestHeaders. Reset();
            HttpResponseHeaders. Reset();
            HttpRequestFeature. Headers = HttpRequestHeaders;
            HttpResponseFeature. Headers = HttpResponseHeaders;

            if (HttpRequestFeature. Scheme == null)
            {
                HttpRequestFeature. Scheme = _context. IsHttps ? " https" : "http";
            }

            _manuallySetRequestAbortToken = null;
            _abortedCts = null;

            // Allow two bytes for \r\n after headers
            _requestHeadersParsed = 0;

            _responseBytesWritten = 0;

            MinRequestBodyDataRate = ServerOptions. Limits. MinRequestBodyDataRate;
            MinResponseDataRate = ServerOptions. Limits. MinResponseDataRate;

            OnReset();
        }

        protected abstract void OnReset();

        protected virtual void OnRequestProcessingEnding()
        {
        }

        protected virtual void OnRequestProcessingEnded()
        {
        }

        protected virtual void BeginRequestProcessing()
        {
        }

        protected virtual bool BeginRead(out ValueAwaiter<ReadResult> awaitable)
        {
            awaitable = default;
            return false;
        }

        protected abstract string CreateRequestId();

        protected abstract MessageBody CreateMessageBody();

        protected abstract bool TryParseRequest(ReadResult result, out bool endConnection);

        private void CancelRequestAbortedToken()
        {
            try
            {
                RequestAbortedSource. Cancel();
                _abortedCts = null;
            }
            catch (Exception ex)
            {
                Log. ApplicationError(ConnectionId, TraceIdentifier, ex);
            }
        }

        /// <summary>
        /// Immediate kill the connection and poison the request and response streams. 
        /// </summary>
        public void Abort(Exception error)
        {
            if (Interlocked. Exchange(ref _requestAborted, 1) == 0)
            {
                _keepAlive = false;

                _streams?.Abort(error);

                Output. Abort(error);

                // Potentially calling user code.  CancelRequestAbortedToken logs any exceptions. 
                ServiceContext. ThreadPool. UnsafeRun(state => ((HttpProtocol)state).CancelRequestAbortedToken(), this);
            }
        }

        public void OnHeader(Span<byte> name, Span<byte> value)
        {
            _requestHeadersParsed++;
            if (_requestHeadersParsed > ServerOptions. Limits. MaxRequestHeaderCount)
            {
                ThrowRequestRejected(RequestRejectionReason. TooManyHeaders);
            }
            // Kooboo: Modify by suning to remove http header ASCII check 
            var valueString = value. GetKoobooAsciiStringNonNullCharacters();

            HttpRequestHeaders. Append(name, valueString);
        }

        public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
        {
            try
            {
                while (_keepAlive)
                {
                    BeginRequestProcessing();

                    var result = default(ReadResult);
                    var endConnection = false;
                    do
                    {
                        if (BeginRead(out var awaitable))
                        {
                            result = await awaitable;
                            if (result. Buffer. IsEmpty)
                            {
                                // Avoid CPU 100% when empty read
                                await Task. Delay(1);
                            }
                        }
                    } 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 && _keepAlive)
                            {
                                // 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. 
                            HttpResponseFeature. 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();
                        }
                    }
                }
            }
            catch (BadHttpRequestException ex)
            {
                // Handle BadHttpRequestException thrown during request line or header parsing. 
                // SetBadRequestState logs the error. 
                SetBadRequestState(ex);
            }
            catch (ConnectionResetException ex)
            {
                // Don't log ECONNRESET errors made between requests.  Browsers like IE will reset connections regularly. 
                if (_requestProcessingStatus ! = RequestProcessingStatus. RequestPending)
                {
                    Log. RequestProcessingError(ConnectionId, ex);
                }
            }
            catch (IOException ex)
            {
                Log. RequestProcessingError(ConnectionId, ex);
            }
            catch (Exception ex)
            {
                Log. LogError(CoreStrings. RequestProcessingEndError, ex);
            }
            finally
            {
                try
                {
                    OnRequestProcessingEnding();
                    await TryProduceInvalidRequestResponse();
                    Output. Dispose();
                }
                catch (Exception ex)
                {
                    Log. LogError(CoreStrings. ConnectionShutdownError, ex);
                }
                finally
                {
                    OnRequestProcessingEnded();
                }
            }
        }

        public void OnStarting(Func<object, Task> callback, object state)
        {
            lock (_onStartingSync)
            {
                if (HasResponseStarted)
                {
                    ThrowResponseAlreadyStartedException(nameof(OnStarting));
                }

                if (_onStarting == null)
                {
                    _onStarting = new Stack<KeyValuePair<Func<object, Task>, object>>();
                }
                _onStarting. Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
            }
        }

        public void OnCompleted(Func<object, Task> callback, object state)
        {
            lock (_onCompletedSync)
            {
                if (_onCompleted == null)
                {
                    _onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
                }
                _onCompleted. Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
            }
        }

        protected Task FireOnStarting()
        {
            Stack<KeyValuePair<Func<object, Task>, object>> onStarting;
            lock (_onStartingSync)
            {
                onStarting = _onStarting;
                _onStarting = null;
            }

            if (onStarting == null)
            {
                return TaskHelpers. CompletedTask();
            }
            else
            {
                return FireOnStartingMayAwait(onStarting);
            }

        }

        private Task FireOnStartingMayAwait(Stack<KeyValuePair<Func<object, Task>, object>> onStarting)
        {
            try
            {
                var count = onStarting. Count;
                for (var i = 0; i < count; i++)
                {
                    var entry = onStarting. Pop();
                    var task = entry. Key. Invoke(entry. Value);
                    if (! ReferenceEquals(task, TaskHelpers. CompletedTask()))
                    {
                        return FireOnStartingAwaited(task, onStarting);
                    }
                }
            }
            catch (Exception ex)
            {
                ReportApplicationError(ex);
            }

            return TaskHelpers. CompletedTask();
        }

        private async Task FireOnStartingAwaited(Task currentTask, Stack<KeyValuePair<Func<object, Task>, object>> onStarting)
        {
            try
            {
                await currentTask;

                var count = onStarting. Count;
                for (var i = 0; i < count; i++)
                {
                    var entry = onStarting. Pop();
                    await entry. Key. Invoke(entry. Value);
                }
            }
            catch (Exception ex)
            {
                ReportApplicationError(ex);
            }
        }

        protected Task FireOnCompleted()
        {
            Stack<KeyValuePair<Func<object, Task>, object>> onCompleted;
            lock (_onCompletedSync)
            {
                onCompleted = _onCompleted;
                _onCompleted = null;
            }

            if (onCompleted == null)
            {
                return TaskHelpers. CompletedTask();
            }
            else
            {
                return FireOnCompletedAwaited(onCompleted);
            }
        }

        private async Task FireOnCompletedAwaited(Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
        {
            foreach (var entry in onCompleted)
            {
                try
                {
                    await entry. Key. Invoke(entry. Value);
                }
                catch (Exception ex)
                {
                    ReportApplicationError(ex);
                }
            }
        }

        public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            if (! HasResponseStarted)
            {
                var initializeTask = InitializeResponseAsync(0);
                // If return is Task. CompletedTask no awaiting is required
                if (! ReferenceEquals(initializeTask, TaskHelpers. CompletedTask()))
                {
                    return FlushAsyncAwaited(initializeTask, cancellationToken);
                }
            }

            return Output. FlushAsync(cancellationToken);
        }

        [MethodImpl(MethodImplOptions. NoInlining)]
        private async Task FlushAsyncAwaited(Task initializeTask, CancellationToken cancellationToken)
        {
            await initializeTask;
            await Output. FlushAsync(cancellationToken);
        }

        public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default(CancellationToken))
        {
            // For the first write, ensure headers are flushed if WriteDataAsync isn't called. 
            var firstWrite = ! HasResponseStarted;

            if (firstWrite)
            {
                var initializeTask = InitializeResponseAsync(data. Count);
                // If return is Task. CompletedTask no awaiting is required
                if (! ReferenceEquals(initializeTask, TaskHelpers. CompletedTask()))
                {
                    return WriteAsyncAwaited(initializeTask, data, cancellationToken);
                }
            }
            else
            {
                VerifyAndUpdateWrite(data. Count);
            }

            if (_canHaveBody)
            {
                if (_autoChunk)
                {
                    if (data. Count == 0)
                    {
                        return ! firstWrite ?  TaskHelpers. CompletedTask() : FlushAsync(cancellationToken);
                    }
                    return WriteChunkedAsync(data, cancellationToken);
                }
                else
                {
                    CheckLastWrite();
                    return Output. WriteDataAsync(data, cancellationToken: cancellationToken);
                }
            }
            else
            {
                HandleNonBodyResponseWrite();
                return ! firstWrite ?  TaskHelpers. CompletedTask() : FlushAsync(cancellationToken);
            }
        }

        public async Task WriteAsyncAwaited(Task initializeTask, ArraySegment<byte> data, CancellationToken cancellationToken)
        {
            await initializeTask;

            // WriteAsyncAwaited is only called for the first write to the body. 
            // Ensure headers are flushed if Write(Chunked)Async isn't called. 
            if (_canHaveBody)
            {
                if (_autoChunk)
                {
                    if (data. Count == 0)
                    {
                        await FlushAsync(cancellationToken);
                        return;
                    }

                    await WriteChunkedAsync(data, cancellationToken);
                }
                else
                {
                    CheckLastWrite();
                    await Output. WriteDataAsync(data, cancellationToken: cancellationToken);
                }
            }
            else
            {
                HandleNonBodyResponseWrite();
                await FlushAsync(cancellationToken);
            }
        }

        private void VerifyAndUpdateWrite(int count)
        {
            var responseHeaders = HttpResponseHeaders;

            if (responseHeaders ! = null &&
                ! responseHeaders. HasTransferEncoding &&
                responseHeaders. ContentLength. HasValue &&
                _responseBytesWritten + count > responseHeaders. ContentLength. Value)
            {
                _keepAlive = false;
                throw new InvalidOperationException(
                    CoreStrings. FormatTooManyBytesWritten(_responseBytesWritten + count, responseHeaders. ContentLength. Value));
            }

            _responseBytesWritten += count;
        }

        private void CheckLastWrite()
        {
            var responseHeaders = HttpResponseHeaders;

            // Prevent firing request aborted token if this is the last write, to avoid
            // aborting the request if the app is still running when the client receives
            // the final bytes of the response and gracefully closes the connection. 
            //
            // Called after VerifyAndUpdateWrite(), so _responseBytesWritten has already been updated. 
            if (responseHeaders ! = null &&
                ! responseHeaders. HasTransferEncoding &&
                responseHeaders. ContentLength. HasValue &&
                _responseBytesWritten == responseHeaders. ContentLength. Value)
            {
                _abortedCts = null;
            }
        }

        protected void VerifyResponseContentLength()
        {
            var responseHeaders = HttpResponseHeaders;

            if (! HttpMethods. IsHead(HttpRequestFeature. Method) &&
                ! responseHeaders. HasTransferEncoding &&
                responseHeaders. ContentLength. HasValue &&
                _responseBytesWritten < responseHeaders. ContentLength. Value)
            {
                // We need to close the connection if any bytes were written since the client
                // cannot be certain of how many bytes it will receive. 
                if (_responseBytesWritten > 0)
                {
                    _keepAlive = false;
                }

                ReportApplicationError(new InvalidOperationException(
                    CoreStrings. FormatTooFewBytesWritten(_responseBytesWritten, responseHeaders. ContentLength. Value)));
            }
        }

        private Task WriteChunkedAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
        {
            return Output. WriteAsync(_writeChunk, data);
        }

        private static void WriteChunk(PipeWriter writableBuffer, ArraySegment<byte> buffer)
        {
            var writer = OutputWriter. Create(writableBuffer);
            if (buffer. Count > 0)
            {
                ChunkWriter. WriteBeginChunkBytes(ref writer, buffer. Count);
                writer. Write(new ReadOnlySpan<byte>(buffer. Array, buffer. Offset, buffer. Count));
                ChunkWriter. WriteEndChunkBytes(ref writer);
            }
        }

        private static ArraySegment<byte> CreateAsciiByteArraySegment(string text)
        {
            var bytes = Encoding. ASCII.GetBytes(text);
            return new ArraySegment<byte>(bytes);
        }

        public void ProduceContinue()
        {
            if (HasResponseStarted)
            {
                return;
            }

            if (_httpVersion ! = Http. HttpVersion. Http10 &&
                HttpRequestFeature. Headers. TryGetValue("Expect", out var expect) &&
                (expect. FirstOrDefault() ?? "") .Equals("100-continue", StringComparison. OrdinalIgnoreCase))
            {
                Output. Write100ContinueAsync(default(CancellationToken)).GetAwaiter().GetResult();
            }
        }

        public Task InitializeResponseAsync(int firstWriteByteCount)
        {
            var startingTask = FireOnStarting();
            // If return is Task. CompletedTask no awaiting is required
            if (! ReferenceEquals(startingTask, TaskHelpers. CompletedTask()))
            {
                return InitializeResponseAwaited(startingTask, firstWriteByteCount);
            }

            if (_applicationException ! = null)
            {
                ThrowResponseAbortedException();
            }

            VerifyAndUpdateWrite(firstWriteByteCount);
            ProduceStart(appCompleted: false);

            return TaskHelpers. CompletedTask();
        }

        [MethodImpl(MethodImplOptions. NoInlining)]
        public async Task InitializeResponseAwaited(Task startingTask, int firstWriteByteCount)
        {
            await startingTask;

            if (_applicationException ! = null)
            {
                ThrowResponseAbortedException();
            }

            VerifyAndUpdateWrite(firstWriteByteCount);
            ProduceStart(appCompleted: false);
        }

        private void ProduceStart(bool appCompleted)
        {
            if (HasResponseStarted)
            {
                return;
            }

            _requestProcessingStatus = RequestProcessingStatus. ResponseStarted;

            CreateResponseHeader(appCompleted);
        }

        protected Task TryProduceInvalidRequestResponse()
        {
            // If _requestAborted is set, the connection has already been closed. 
            if (_requestRejectedException ! = null && _requestAborted == 0)
            {
                return ProduceEnd();
            }

            return TaskHelpers. CompletedTask();
        }

        protected Task ProduceEnd()
        {
            if (_requestRejectedException ! = null || _applicationException ! = null)
            {
                if (HasResponseStarted)
                {
                    // We can no longer change the response, so we simply close the connection. 
                    _keepAlive = false;
                    return TaskHelpers. CompletedTask();
                }

                // If the request was rejected, the error state has already been set by SetBadRequestState and
                // that should take precedence. 
                if (_requestRejectedException ! = null)
                {
                    SetErrorResponseException(_requestRejectedException);
                }
                else
                {
                    // 500 Internal Server Error
                    SetErrorResponseHeaders(statusCode: StatusCodes. Status500InternalServerError);
                }
            }

            if (! HasResponseStarted)
            {
                return ProduceEndAwaited();
            }

            return WriteSuffix();
        }

        [MethodImpl(MethodImplOptions. NoInlining)]
        private async Task ProduceEndAwaited()
        {
            ProduceStart(appCompleted: true);

            // Force flush
            await Output. FlushAsync(default(CancellationToken));

            await WriteSuffix();
        }

        private Task WriteSuffix()
        {
            // _autoChunk should be checked after we are sure ProduceStart() has been called
            // since ProduceStart() may set _autoChunk to true. 
            if (_autoChunk || _httpVersion == Http. HttpVersion. Http2)
            {
                return WriteSuffixAwaited();
            }

            if (_keepAlive)
            {
                Log. ConnectionKeepAlive(ConnectionId);
            }

            if (HttpMethods. IsHead(HttpRequestFeature. Method) && _responseBytesWritten > 0)
            {
                Log. ConnectionHeadResponseBodyWrite(ConnectionId, _responseBytesWritten);
            }

            return TaskHelpers. CompletedTask();
        }

        private async Task WriteSuffixAwaited()
        {
            // For the same reason we call CheckLastWrite() in Content-Length responses. 
            _abortedCts = null;

            await Output. WriteStreamSuffixAsync(default(CancellationToken));

            if (_keepAlive)
            {
                Log. ConnectionKeepAlive(ConnectionId);
            }

            if (HttpMethods. IsHead(HttpRequestFeature. Method) && _responseBytesWritten > 0)
            {
                Log. ConnectionHeadResponseBodyWrite(ConnectionId, _responseBytesWritten);
            }
        }

        private void CreateResponseHeader(bool appCompleted)
        {
            var responseHeaders = HttpResponseHeaders;
            var hasConnection = responseHeaders. HasConnection;
            var connectionOptions = HttpHeaders. ParseConnection(responseHeaders. HeaderConnection);
            var hasTransferEncoding = responseHeaders. HasTransferEncoding;
            var transferCoding = HttpHeaders. GetFinalTransferCoding(responseHeaders. HeaderTransferEncoding);

            if (_keepAlive && hasConnection && (connectionOptions & ConnectionOptions. KeepAlive) ! = ConnectionOptions. KeepAlive)
            {
                _keepAlive = false;
            }

            // https://tools.ietf.org/html/rfc7230#section-3.3.1
            // If any transfer coding other than
            // chunked is applied to a response payload body, the sender MUST either
            // apply chunked as the final transfer coding or terminate the message
            // by closing the connection. 
            if (hasTransferEncoding && transferCoding ! = TransferCoding. Chunked)
            {
                _keepAlive = false;
            }

            // Set whether response can have body
            _canHaveBody = StatusCanHaveBody(HttpResponseFeature. StatusCode) && HttpRequestFeature. Method ! = "HEAD";

            // Don't set the Content-Length or Transfer-Encoding headers
            // automatically for HEAD requests or 204, 205, 304 responses. 
            if (_canHaveBody)
            {
                if (! hasTransferEncoding && ! responseHeaders. ContentLength. HasValue)
                {
                    if (appCompleted && HttpResponseFeature. StatusCode ! = StatusCodes. Status101SwitchingProtocols)
                    {
                        // Since the app has completed and we are only now generating
                        // the headers we can safely set the Content-Length to 0. 
                        responseHeaders. ContentLength = 0;
                    }
                    else
                    {
                        // Note for future reference: never change this to set _autoChunk to true on HTTP/1.0
                        // connections, even if we were to infer the client supports it because an HTTP/1.0 request
                        // was received that used chunked encoding.  Sending a chunked response to an HTTP/1.0
                        // client would break compliance with RFC 7230 (section 3.3.1):
                        //
                        // A server MUST NOT send a response containing Transfer-Encoding unless the corresponding
                        // request indicates HTTP/1.1 (or later). 
                        //
                        // This also covers HTTP/2, which forbids chunked encoding in RFC 7540 (section 8.1:
                        //
                        // The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2. 
                        if (_httpVersion == Http. HttpVersion. Http11 && HttpResponseFeature. StatusCode ! = StatusCodes. Status101SwitchingProtocols)
                        {
                            _autoChunk = true;
                            responseHeaders. SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
                        }
                        else
                        {
                            _keepAlive = false;
                        }
                    }
                }
            }
            else if (hasTransferEncoding)
            {
                RejectNonBodyTransferEncodingResponse(appCompleted);
            }

            responseHeaders. SetReadOnly();

            if (! hasConnection && _httpVersion ! = Http. HttpVersion. Http2)
            {
                if (!_ keepAlive)
                {
                    responseHeaders. SetRawConnection("close", _bytesConnectionClose);
                }
                else if (_httpVersion == Http. HttpVersion. Http10)
                {
                    responseHeaders. SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
                }
            }

            if (ServerOptions. AddServerHeader && ! responseHeaders. HasServer)
            {
                responseHeaders. SetRawServer(Constants. ServerName, _bytesServer);
            }

            if (! responseHeaders. HasDate)
            {
                var dateHeaderValues = DateHeaderValueManager. GetDateHeaderValues();
                responseHeaders. SetRawDate(dateHeaderValues. String, dateHeaderValues. Bytes);
            }

            Output. WriteResponseHeaders(HttpResponseFeature. StatusCode,HttpResponseFeature. ReasonPhrase, responseHeaders);
        }

        public bool StatusCanHaveBody(int statusCode)
        {
            // List of status codes taken from Microsoft. Net. Http. Server. Response
            return statusCode ! = StatusCodes. Status204NoContent &&
                   statusCode ! = StatusCodes. Status205ResetContent &&
                   statusCode ! = StatusCodes. Status304NotModified;
        }

        private void ThrowResponseAlreadyStartedException(string value)
        {
            throw new InvalidOperationException(CoreStrings. FormatParameterReadOnlyAfterResponseStarted(value));
        }

        private void RejectNonBodyTransferEncodingResponse(bool appCompleted)
        {
            var ex = new InvalidOperationException(CoreStrings. FormatHeaderNotAllowedOnResponse("Transfer-Encoding", HttpResponseFeature. StatusCode));
            if (! appCompleted)
            {
                // Back out of header creation surface exeception in user code
                _requestProcessingStatus = RequestProcessingStatus. AppStarted;
                throw ex;
            }
            else
            {
                ReportApplicationError(ex);

                // 500 Internal Server Error
                SetErrorResponseHeaders(statusCode: StatusCodes. Status500InternalServerError);
            }
        }

        private void SetErrorResponseException(BadHttpRequestException ex)
        {
            SetErrorResponseHeaders(ex. StatusCode);

            if (! StringValues. IsNullOrEmpty(ex. AllowedHeader))
            {
                HttpResponseHeaders. HeaderAllow = ex. AllowedHeader;
            }
        }

        private void SetErrorResponseHeaders(int statusCode)
        {
            Debug. Assert(!HasResponseStarted, $"{nameof(SetErrorResponseHeaders)} called after response had already started."); 

            HttpResponseFeature. StatusCode = statusCode;
            HttpResponseFeature. ReasonPhrase = null;

            var responseHeaders = HttpResponseHeaders;
            responseHeaders. Reset();
            var dateHeaderValues = DateHeaderValueManager. GetDateHeaderValues();

            responseHeaders. SetRawDate(dateHeaderValues. String, dateHeaderValues. Bytes);

            responseHeaders. ContentLength = 0;

            if (ServerOptions. AddServerHeader)
            {
                responseHeaders. SetRawServer(Constants. ServerName, _bytesServer);
            }
        }

        public void HandleNonBodyResponseWrite()
        {
            // Writes to HEAD response are ignored and logged at the end of the request
            if (HttpRequestFeature. Method ! = "HEAD")
            {
                // Throw Exception for 204, 205, 304 responses. 
                throw new InvalidOperationException(CoreStrings. FormatWritingToResponseBodyNotSupported(HttpResponseFeature. StatusCode));
            }
        }

        private void ThrowResponseAbortedException()
        {
            throw new ObjectDisposedException(CoreStrings. UnhandledApplicationException, _applicationException);
        }

        public void ThrowRequestRejected(RequestRejectionReason reason)
        {
            throw BadHttpRequestException. GetException(reason);
        }

        public void ThrowRequestRejected(RequestRejectionReason reason, string detail)
            => throw BadHttpRequestException. GetException(reason, detail);

        public void ThrowRequestTargetRejected(Span<byte> target)
            => throw GetInvalidRequestTargetException(target);

        private BadHttpRequestException GetInvalidRequestTargetException(Span<byte> target)
            => BadHttpRequestException. GetException(
                RequestRejectionReason. InvalidRequestTarget,
                Log. IsEnabled(LogLevel. Information)
                    ?  target. GetAsciiStringEscaped(Constants. MaxExceptionDetailSize)
                    : string. Empty);

        public void SetBadRequestState(RequestRejectionReason reason)
        {
            SetBadRequestState(BadHttpRequestException. GetException(reason));
        }

        public void SetBadRequestState(BadHttpRequestException ex)
        {
            Log. ConnectionBadRequest(ConnectionId, ex);

            if (! HasResponseStarted)
            {
                SetErrorResponseException(ex);
            }

            _keepAlive = false;
            _requestRejectedException = ex;
        }

        protected void ReportApplicationError(Exception ex)
        {
            if (_applicationException == null)
            {
                _applicationException = ex;
            }
            else if (_applicationException is AggregateException)
            {
                _applicationException = new AggregateException(_applicationException, ex).Flatten();
            }
            else
            {
                _applicationException = new AggregateException(_applicationException, ex);
            }

            Log. ApplicationError(ConnectionId, TraceIdentifier, ex);
        }

        private Pipe CreateRequestBodyPipe()
            => new Pipe(new PipeOptions
            (
                pool: _context. MemoryPool,
                //readerScheduler: ServiceContext. ThreadPool,
                //writerScheduler: PipeScheduler. Inline,
                pauseWriterThreshold: 1,
                resumeWriterThreshold: 1
            ));
    }
}
