private HttpClient CreateHttpClient(IFluentProxySettings settings) { HttpClient httpClient = _httpClientFactory.CreateClient(settings.ExternalUrl.Authority); httpClient.BaseAddress = settings.ExternalUrl; return(httpClient); }
public FluentProxyServer(IFluentProxySettings settings, IWebHost webHost, Action onDispose) { _onDispose = onDispose; Settings = settings; WebHost = webHost; _internalHttpClient = new Lazy <HttpClient>(() => new HttpClient { BaseAddress = settings.ProxyUrl, }); }
private async Task <FluentProxyServer> CreateServerInternal(IFluentProxySettings settings, string serverKey, CancellationToken cancellationToken) { IWebHost webHost = new WebHostBuilder() .UseKestrel() .UseUrls($"http://localhost:{settings.InternalPort}") .ConfigureServices(services => services.AddSingleton <IFluentProxySettings>(settings)) .UseStartup <FluentProxyStartup>() .Build(); await webHost.StartAsync(cancellationToken); return(new FluentProxyServer(settings, webHost, () => _webHosts.TryRemove(serverKey, out _))); }
/// <summary> /// Initializes a new instance of the <see cref="FluentProxySettings"/> class. /// </summary> public FluentProxySettings(IFluentProxySettings settings) { InternalPort = settings.InternalPort; ExternalUrl = settings.ExternalUrl; ProxyUrl = settings.ProxyUrl; Timeout = settings.Timeout; CreateHttpClient = settings.CreateHttpClient; InitializeHttpClient = settings.InitializeHttpClient; OnRequestStarted = settings.OnRequestStarted; OnRequestFinished = settings.OnRequestFinished; GetCachedResponse = settings.GetCachedResponse; CopyHeadersFromRequest = settings.CopyHeadersFromRequest; CopyHeadersFromResponse = settings.CopyHeadersFromResponse; RequestHeadersNoCopy = settings.RequestHeadersNoCopy; ResponseHeadersNoCopy = settings.ResponseHeadersNoCopy; }
/// <summary> /// Creates and starts proxy server. /// </summary> /// <param name="settings">Settings.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>Task representing asynchronous operation.</returns> public Task <FluentProxyServer> CreateServer(IFluentProxySettings settings, CancellationToken cancellationToken = default) { // Server key before port evaluation. That means that same url and undefined port returns same servers. string serverKey = ServerKey(settings); int internalPort = settings.InternalPort; if (internalPort <= 0) { internalPort = TcpUtils.FindFreeTcpPort(); } // Recreate with port and proxyUrl settings = new FluentProxySettings(settings) { InternalPort = internalPort, ProxyUrl = new UriBuilder("http", "localhost", internalPort, settings.ExternalUrl.PathAndQuery).Uri, }; return(_webHosts.GetOrAdd(serverKey, url => CreateServerInternal(settings, serverKey, cancellationToken))); }
private async Task ProcessRequest(HttpContext httpContext, IFluentProxySettings settings) { HttpClient httpClient = settings.CreateHttpClient != null?settings.CreateHttpClient(settings) : CreateHttpClient(settings); if (settings.Timeout.HasValue) { httpClient.Timeout = settings.Timeout.Value; } if (settings.InitializeHttpClient != null) { httpClient = settings.InitializeHttpClient(httpClient, settings); } HttpRequest httpRequest = httpContext.Request; HttpResponse httpResponse = httpContext.Response; // Get full external Url Uri externalUriFull; string requestPathAndQuery = httpRequest.GetEncodedPathAndQuery(); if (settings.GetRequestUrl != null) { // todo: use its value? externalUriFull = settings.GetRequestUrl(settings, httpRequest); } else { externalUriFull = new Uri(settings.ExternalUrl, requestPathAndQuery); } var session = new RequestSession { RequestId = Guid.NewGuid().ToString(), // httpContext.TraceIdentifier? // todo external ID RequestTime = DateTime.Now, RequestUrl = externalUriFull, RequestHeaders = httpRequest.Headers.ToDictionary(pair => pair.Key, pair => pair.Value.ToString()), RequestContent = null,//todo: read and rewind content if set in settings }; // todo: to docs httpContext.Items["FluentProxySession"] = session; // Create http request HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod(httpRequest.Method), requestPathAndQuery); // Fill request headers if (settings.CopyHeadersFromRequest) { foreach (var requestHeader in httpRequest.Headers) { if (settings.RequestHeadersNoCopy != null && settings.RequestHeadersNoCopy.Contains(requestHeader.Key, StringComparer.InvariantCultureIgnoreCase)) { continue; } if (!httpRequestMessage.Headers.TryAddWithoutValidation(requestHeader.Key, requestHeader.Value.ToArray()) && httpRequestMessage.Content != null) { httpRequestMessage.Content?.Headers.TryAddWithoutValidation(requestHeader.Key, requestHeader.Value.ToArray()); } } } httpRequestMessage.Headers.Host = settings.ExternalUrl.Authority; // Fill request body if (httpRequest.ContentLength.HasValue) { httpRequestMessage.Content = new StreamContent(httpRequest.Body); } try { settings.OnRequestStarted?.Invoke(session); } catch (Exception e) { _logger.LogWarning(e, "IFluentProxySettings.OnRequestStarted error."); } try { // Try get response from cache ResponseData responseData = settings.GetCachedResponse?.Invoke(session); if (responseData != null && responseData.IsOk) { session.ResponseData = responseData; session.ResponseSource = ResponseSource.Cache; httpResponse.StatusCode = responseData.StatusCode; // Fill response headers if (settings.CopyHeadersFromResponse) { foreach (var responseHeader in responseData.ResponseHeaders) { if (settings.ResponseHeadersNoCopy != null && settings.ResponseHeadersNoCopy.Contains(responseHeader.Key, StringComparer.InvariantCultureIgnoreCase)) { continue; } httpResponse.Headers[responseHeader.Key] = responseHeader.Value; } } // SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response. httpResponse.Headers.Remove("transfer-encoding"); } else { // Invoke real http request HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(httpRequestMessage); responseData = new ResponseData { RequestId = session.RequestId, ResponseId = Guid.NewGuid().ToString(), StatusCode = (int)httpResponseMessage.StatusCode, }; httpResponse.StatusCode = responseData.StatusCode; // Fill response headers if (settings.CopyHeadersFromResponse) { foreach (var responseHeader in httpResponseMessage.Headers) { if (settings.ResponseHeadersNoCopy != null && settings.ResponseHeadersNoCopy.Contains(responseHeader.Key, StringComparer.InvariantCultureIgnoreCase)) { continue; } httpResponse.Headers[responseHeader.Key] = responseHeader.Value.ToArray(); } foreach (var responseHeader in httpResponseMessage.Content.Headers) { if (settings.ResponseHeadersNoCopy != null && settings.ResponseHeadersNoCopy.Contains(responseHeader.Key, StringComparer.InvariantCultureIgnoreCase)) { continue; } httpResponse.Headers[responseHeader.Key] = responseHeader.Value.ToArray(); } } // SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response. httpResponse.Headers.Remove("transfer-encoding"); // Fill headers responseData.ResponseHeaders = httpResponse.Headers.ToDictionary(pair => pair.Key, pair => pair.Value.ToString()); // Read content string responseText = await httpResponseMessage.Content.ReadAsStringAsync(); responseData.ResponseTime = DateTime.Now; responseData.ResponseContent = responseText; session.ResponseData = responseData; session.ResponseSource = ResponseSource.HttpResponse; } // Write response content if (session.ResponseData.ResponseContent != null) { await httpResponse.WriteAsync(session.ResponseData.ResponseContent); } } catch (Exception e) { _logger.LogError(e, "Error in request processing."); httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError; if (session.ResponseData == null) { session.ResponseData = new ResponseData(); } session.ResponseData.Exception = e; session.ResponseData.StatusCode = (int)HttpStatusCode.InternalServerError; } finally { try { settings.OnRequestFinished?.Invoke(session); } catch (Exception e) { _logger.LogWarning(e, "IFluentProxySettings.OnRequestFinished error."); } } }
public Task InvokeAsync(HttpContext context, IFluentProxySettings settings) { return(ProcessRequest(context, settings)); }
private string ServerKey(IFluentProxySettings settings) => $"{settings.ExternalUrl.AbsoluteUri};{settings.InternalPort}";
// todo internal static IServiceCollection AddProxy(this IServiceCollection services, IFluentProxySettings settings) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.TryAddSingleton <IFluentProxyFactory, FluentProxyFactory>(); var server = new FluentProxyFactory().CreateServer(settings).Result; services.AddHttpClient <ProxiedHttpClient>(client => { client.BaseAddress = settings.ExternalUrl; settings.InitializeHttpClient?.Invoke(client, settings); }); return(services); }