private async Task Proxy(IProxyConfiguration proxy, HttpContext context)
        {
            var    id   = Guid.NewGuid().ToString("D");
            string path = context.Request.Path + context.Request.QueryString;

            //_logger.LogInformation($"{id} start {context.Request.Method} {path}");

            path = CommonUtility.CombineWithSlash(proxy.Target, path.Substring(proxy.Path.Length));

            if (context.WebSockets.IsWebSocketRequest)
            {
                await HandleWebSocket(id, proxy, path, context);

                return;
            }

            HttpRequestMessage request = new HttpRequestMessage(GetHttpMethod(context.Request.Method, out var hasBody), path);

            //request.Version = HttpVersion.Version11;

            if (!hasBody.HasValue)
            {
                hasBody = context.Request.Headers.ContainsKey("Transfer-Encoding") || context.Request.Headers.ContainsKey("Content-Length");
            }
            HttpContent content = null;

            if (hasBody.Value)
            {
                content         = new StreamContent(context.Request.Body);
                request.Content = content;
            }

            if (proxy.Headers != null)
            {
                foreach (var kv in proxy.Headers)
                {
                    bool success;
                    if (kv.Key.StartsWith("Content-", StringComparison.Ordinal))
                    {
                        success = content?.Headers.TryAddWithoutValidation(kv.Key, kv.Value) ?? false;
                    }
                    else
                    {
                        success = request.Headers.TryAddWithoutValidation(kv.Key, kv.Value);
                    }
                    if (!success)
                    {
                        _logger.LogWarning($"Failed to add header {kv.Key}: {kv.Value}.");
                    }
                }
            }

            foreach (var kv in context.Request.Headers)
            {
                if (proxy.RemoveHeaders != null)
                {
                    if (proxy.RemoveHeaders.Contains(kv.Key))
                    {
                        continue;
                    }
                }
                // strip 'Transfer-Encoding: chunked' the whole request is send, we are not a transport level proxy
                if (string.Equals("Transfer-Encoding", kv.Key, StringComparison.Ordinal) ||
                    string.Equals("Expect", kv.Key, StringComparison.Ordinal) ||
                    string.Equals("Host", kv.Key, StringComparison.Ordinal))
                //string.Equals("Connection", kv.Key, StringComparison.Ordinal))
                {
                    _logger.LogDebug($"Stripping request {kv.Key}: {kv.Value}");
                    continue;
                }

                bool success;
                // Content-Length, Content-Type
                if (kv.Key.StartsWith("Content-", StringComparison.Ordinal))
                {
                    success = content?.Headers.TryAddWithoutValidation(kv.Key, (IEnumerable <string>)kv.Value) ?? false;
                }
                else
                {
                    success = request.Headers.TryAddWithoutValidation(kv.Key, (IEnumerable <string>)kv.Value);
                }
                if (!success && !kv.Key.StartsWith(":", StringComparison.Ordinal))
                {
                    _logger.LogWarning($"Failed to add header {kv.Key}: {kv.Value}.");
                }
            }

            var client = GetHttpClient(proxy);

            try {
                Task <HttpResponseMessage> responseTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                HttpResponseMessage        response     = null;
                try {
                    response = await responseTask.ConfigureAwait(false);
                }
                catch (Exception ex) {
                    _logger.LogInformation($"{id} failed {context.Request.Method} {path} - {ex.Message}");
                    throw;
                }

                using (response) {
                    context.Response.StatusCode = (int)response.StatusCode;

                    foreach (var header in response.Headers)
                    {
                        // strip 'Transfer-Encoding: chunked' the whole response is read, we are not a transport level proxy
                        if (string.Equals("Transfer-Encoding", header.Key, StringComparison.Ordinal) ||
                            string.Equals("Expect", header.Key, StringComparison.Ordinal) ||
                            string.Equals("Host", header.Key, StringComparison.Ordinal))
                        //string.Equals("Connection", header.Key, StringComparison.Ordinal))
                        {
                            _logger.LogDebug($"Stripping response {header.Key}: {string.Join(", ", header.Value)}");
                            continue;
                        }
                        context.Response.Headers[header.Key] = header.Value.ToArray();
                    }

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

                    using (var stream = await response.Content.ReadAsStreamAsync()) {
                        await stream.CopyToAsync(context.Response.Body);
                    }

                    //_logger.LogInformation($"{id} done {(int)response.StatusCode} {context.Request.Method} {path}");
                }
            }
            finally {
                if (proxy.NewHttpClient)
                {
                    client.Dispose();
                }
            }
        }