具有 HttpClientFactory 实现的动态代理

Posted

技术标签:

【中文标题】具有 HttpClientFactory 实现的动态代理【英文标题】:Having dynamic proxy with HttpClientFactory implementation 【发布时间】:2019-04-08 12:59:46 【问题描述】:

我有Asp.Net Core WebApi。我正在根据HttpClientFactory pattern 发出 Http 请求。这是我的示例代码:

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 个 approuces,其中没有一个对我来说似乎不错:

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.Dispose 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 模式,它可能会导致应用程序性能出现问题,如下面的article 所述

是否有第三种方法可以在不重新创建 HttpClient 的情况下动态更改代理?

【问题讨论】:

据此贴:docs.microsoft.com/en-us/dotnet/standard/… 每次从 IHttpClientFactory 获取 HttpClient 对象时,都会返回一个新实例。但是每个 HttpClient 使用一个由 IHttpClientFactory 汇集和重用的 HttpMessageHandler 来减少资源消耗,只要 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);
        ...
    

【讨论】:

如果事先不知道代理网址怎么办? 那你真的不走运吗?但是,在什么情况下您会事先不知道这样的事情?您可能直到运行时才知道要使用 哪个 代理,但您至少知道可能性。您为每种可能性配置命名客户端,然后您可以在运行时轻松切换它们。【参考方案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);

然后我在 IoC 中注册我的 ProxyHttpHandlerProxyOptions

public IForksCoreConfigurationBuilder ConfigureProxy(Action<ProxyOptions> options)

    Services.AddOptions<ProxyOptions>().Configure(options);
    Services.AddTransient<ProxyHttpHandler>();
    Services.AddHttpClient<IService, MyService>()
            .ConfigurePrimaryHttpMessageHandler<ProxyHttpHandler>();

    return this;

【讨论】:

在第二个请求中你不能再次设置代理,有一个异常:这个实例已经启动了一个或多个请求

以上是关于具有 HttpClientFactory 实现的动态代理的主要内容,如果未能解决你的问题,请参考以下文章

.NET Core HttpClientFactory+Consul实现服务发现

asp.net core 使用HttpClientFactory Polly实现熔断降级

HttpClientFactory与Steeltoe结合来完成服务发现

ASP.NET Core 2.1 中的 HttpClientFactory (Part 3) 使用Handler实现传出请求中间件

使用 .NET 4.6.2 中的 HttpClientFactory

HttpClientFactory.Create 与新的 HttpClient