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(); } } }