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 _)));
        }
예제 #4
0
 /// <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}";
예제 #9
0
        // 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);
        }