Наличие динамического прокси с реализацией HttpClientFactory

#c# #asp.net-core #dotnet-httpclient

#c# #asp.net-ядро #dotnet-httpclient

Вопрос:

У меня есть Asp.Net Core WebApi . Я делаю Http-запросы в соответствии с шаблоном HttpClientFactory. Вот мой пример кода:

 public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHttpClient<IMyInterface, MyService>();
    ...
}

public class MyService: IMyInterface
{
    private readonly HttpClient _client;

    public MyService(HttpClient client)
    {
        _client = client;
    }

    public async Task CallHttpEndpoint()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "www.customUrl.com");
        var response = await _client.SendAsync(request);
        ...
    }

}
  

Я хочу реализовать отправку запросов через динамический прокси. По сути, это означает, что мне может потребоваться менять прокси с каждым запросом. На данный момент я нахожу 2 варианта, ни один из которых мне не кажется хорошим:

1. Иметь статический прокси, подобный этому:

 public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHttpClient<IMyInterface, MyService>().ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler
            {
                Proxy = new WebProxy("http://127.0.0.1:8888"),
                UseProxy = true
            };
        });
    ...
}
  

Но при таком подходе у меня может быть только один прокси для каждой службы.

2. Утилизируйте HttpClient с каждым запросом:

     HttpClientHandler handler = new HttpClientHandler()
    {
        Proxy = new WebProxy("http://127.0.0.1:8888"),
        UseProxy = true,
    };

    using(var client = new HttpClient(handler))
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "www.customUrl.com");
        var response = await client.SendAsync(request);
        ...
    }
  

Но таким образом я нарушаю шаблон HttpClientFactory, и это может вызвать проблемы с производительностью приложения, как указано в следующей статье

Есть ли третий способ, при котором я мог бы динамически изменять прокси без повторного создания HttpClient ?

Комментарии:

1. Согласно этому сообщению: learn.microsoft.com/en-us/dotnet/standard/… Каждый раз, когда вы получаете объект HttpClient из IHttpClientFactory, возвращается новый экземпляр. Но каждый HttpClient использует HttpMessageHandler, который объединяется в пул и повторно используется IHttpClientFactory для уменьшения потребления ресурсов, пока срок службы HttpMessageHandler не истек. Таким образом, он ограничен, но с привилегиями.

Ответ №1:

Невозможно изменить какое-либо из свойств HttpClientHandler или назначить новую версию HttpClientHandler существующей HttpClient после ее создания. Таким образом, тогда невозможно иметь динамический прокси для конкретного HttpClient : вы можете указать только один прокси.

Правильный способ добиться этого — вместо этого использовать именованные клиенты и определять клиента для каждой конечной точки прокси. Затем вам нужно ввести IHttpClientFactory и выбрать один из используемых прокси-серверов, запрашивая именованный клиент, который это реализует.

 services.AddHttpClient("MyServiceProxy1").ConfigurePrimaryHttpMessageHandler(() =>
{
    return new HttpClientHandler
    {
        Proxy = new WebProxy("http://127.0.0.1:8888"),
        UseProxy = true
    };
});

services.AddHttpClient("MyServiceProxy2").ConfigurePrimaryHttpMessageHandler(() =>
{
    return new HttpClientHandler
    {
        Proxy = new WebProxy("http://127.0.0.1:8889"),
        UseProxy = true
    };
});

...
  

Затем:

 public class MyService : IMyInterface
{
    private readonly HttpClient _client;

    public MyService(IHttpClientFactory httpClientFactory)
    {
        _client = httpClientFactory.CreateClient("MyServiceProxy1");
    }

    public async Task CallHttpEndpoint()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "www.customUrl.com");
        var response = await _client.SendAsync(request);
        ...
    }
}
  

Комментарии:

1. Что делать, если вы не знаете URL прокси заранее?

2. Тогда вам действительно не повезло? В каком сценарии вы бы не знали что-то подобное заранее? Возможно, вы не знаете, какой прокси использовать до выполнения, но вы, по крайней мере, знаете возможности. Вы настраиваете именованные клиенты для каждой из возможностей, а затем можете легко переключать их во время выполнения.

Ответ №2:

Я могу сделать это, унаследовав от HttpClientHandler:

 public class ProxyHttpHandler : HttpClientHandler
{
    private int currentProxyIndex = 0;

private ProxyOptions proxyOptions;

public ProxyHttpHandler(IOptions<ProxyOptions> options)
{
    proxyOptions = options != null ? options.Value : throw new ArgumentNullException(nameof(options));
    UseProxy = true;
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    var proxy = proxyOptions.Proxies[currentProxyIndex];
    var proxyResolver = new WebProxy(proxy.Host, proxy.Port)
    {
        Credentials = proxy.Credentials
    };

    Proxy = proxyResolver;

    currentProxyIndex  ;
    if(currentProxyIndex >= proxyOptions.Proxies.Count)
        currentProxyIndex = 0;

    return base.SendAsync(request, cancellationToken);
}
  

}

Затем я регистрирую свой ProxyHttpHandler и ProxyOptions в IoC:

 public IForksCoreConfigurationBuilder ConfigureProxy(Action<ProxyOptions> options)
{
    Services.AddOptions<ProxyOptions>().Configure(options);
    Services.AddTransient<ProxyHttpHandler>();
    Services.AddHttpClient<IService, MyService>()
            .ConfigurePrimaryHttpMessageHandler<ProxyHttpHandler>();

    return this;
}
  

Комментарии:

1. Во втором запросе вы не можете снова установить прокси, есть исключение: этот экземпляр уже запустил один или несколько запросов