/// <summary>
        /// default init
        /// </summary>
        public ProxyMiddleware()
        {
            _defaultOptions = new InternalProxyOptions()
            {
                SendChunked = false
            };

            // if port is not specified default one is taken
            if (!_defaultOptions.Port.HasValue)
            {
                if (string.Equals(_defaultOptions.Scheme, "https", StringComparison.OrdinalIgnoreCase))
                {
                    _defaultOptions.Port = 443;
                }
                else
                {
                    _defaultOptions.Port = 80;
                }
            }

            //if no scheme is choosen, http is taken
            if (string.IsNullOrEmpty(_defaultOptions.Scheme))
            {
                _defaultOptions.Scheme = "http";
            }

            _httpClient = new HttpClient(_defaultOptions.BackChannelMessageHandler ?? new HttpClientHandler());
        }
        /// <summary>
        /// Handle also Web socket, calling pump method inside
        /// </summary>
        /// <param name="context"></param>
        /// <param name="_options"></param>
        /// <param name="destination"></param>
        /// <param name="host"></param>
        /// <param name="port"></param>
        /// <param name="scheme"></param>
        /// <returns></returns>
        private async Task HandleWebSocketRequest(HttpContext context, InternalProxyOptions _options, Node destination, string host, int port, string scheme)
        {
            using (var client = new ClientWebSocket())
            {
                foreach (var headerEntry in context.Request.Headers)
                {
                    if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase))
                    {
                        client.Options.SetRequestHeader(headerEntry.Key, headerEntry.Value);
                    }
                }

                var    wsScheme = string.Equals(destination.Scheme, "https", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws";
                string url      = GetUri(context, host, port, scheme);

                if (_options.WebSocketKeepAliveInterval.HasValue)
                {
                    client.Options.KeepAliveInterval = _options.WebSocketKeepAliveInterval.Value;
                }

                try
                {
                    await client.ConnectAsync(new Uri(url), context.RequestAborted);
                }
                catch (WebSocketException)
                {
                    context.Response.StatusCode = 400;
                    return;
                }

                using (var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol))
                {
                    await Task.WhenAll(PumpWebSocket(context, client, server, _options, context.RequestAborted), PumpWebSocket(context, server, client, _options, context.RequestAborted));
                }
            }
        }
        /// <summary>
        /// Handle a simple http request dumping remote content to the client
        /// </summary>
        /// <param name="context"></param>
        /// <param name="_options"></param>
        /// <param name="destination"></param>
        /// <param name="host"></param>
        /// <param name="port"></param>
        /// <param name="scheme"></param>
        /// <returns></returns>
        private async Task HandleHttpRequest(HttpContext context, InternalProxyOptions _options, Node destination, string host, int port, string scheme)
        {
            var requestMessage = new HttpRequestMessage();
            var requestMethod  = context.Request.Method;

            if (!HttpMethods.IsGet(requestMethod) && !HttpMethods.IsHead(requestMethod) && !HttpMethods.IsDelete(requestMethod) && !HttpMethods.IsTrace(requestMethod))
            {
                var streamContent = new StreamContent(context.Request.Body);
                requestMessage.Content = streamContent;
            }

            // All request headers and cookies must be transferend to remote server. Some headers will be skipped
            foreach (var header in context.Request.Headers)
            {
                if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
                {
                    requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                }
            }


            requestMessage.Headers.Host = host;
            //recreate remote url
            string uriString = GetUri(context, host, port, scheme);

            requestMessage.RequestUri = new Uri(uriString);
            requestMessage.Method     = new HttpMethod(context.Request.Method);
            using (var responseMessage = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
            {
                context.Response.StatusCode = (int)responseMessage.StatusCode;
                foreach (var header in responseMessage.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }

                foreach (var header in responseMessage.Content.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }


                if (!_options.SendChunked)
                {
                    //tell to the browser that response is not chunked
                    context.Response.Headers.Remove("transfer-encoding");
                    await responseMessage.Content.CopyToAsync(context.Response.Body);
                }
                else
                {
                    var buffer = new byte[_options.BufferSize ?? DefaultBufferSize];

                    using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
                    {
                        //long pos = responseStream.Position;
                        //if (pos > 0)
                        //{
                        //    responseStream.Seek(0, SeekOrigin.Begin);
                        //}
                        //context.Response.Body = new MemoryStream();

                        int len  = 0;
                        int full = 0;
                        while ((len = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                        {
                            await context.Response.Body.WriteAsync(buffer, 0, buffer.Length);

                            // await context.Response.Body.FlushAsync();
                            full += buffer.Length;
                        }
                        // context.Response.ContentLength = full;

                        context.Response.Headers.Remove("transfer-encoding");
                    }
                }
            }
        }
        /// <summary>
        /// Core pump method
        /// </summary>
        /// <param name="context"></param>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="_options"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task PumpWebSocket(HttpContext context, WebSocket source, WebSocket destination, InternalProxyOptions _options, CancellationToken cancellationToken)
        {
            var buffer = new byte[_options.BufferSize ?? DefaultBufferSize];

            while (true)
            {
                WebSocketReceiveResult result;
                try
                {
                    result = await source.ReceiveAsync(new ArraySegment <byte>(buffer), cancellationToken);
                }
                catch (OperationCanceledException)
                {
                    await destination.CloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, null, cancellationToken);

                    return;
                }
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await destination.CloseOutputAsync(source.CloseStatus.Value, source.CloseStatusDescription, cancellationToken);

                    return;
                }

                await destination.SendAsync(new ArraySegment <byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
            }
        }