使用 .NET Flurl/HttpClient 设置每个请求代理(或轮换代理)

问题描述 投票:0回答:2

我知道使用 Flurl HTTP .NET 库,我可以使用自定义

HttpClientFactory
设置全局代理,但是 有没有办法为每个请求选择自定义代理

对于许多其他编程语言,设置代理就像设置选项一样简单。例如,使用 Node.js 我可以这样做:

const request = require('request');
let opts = { url: 'http://random.org', proxy: 'http://myproxy' };
request(opts, callback);

使用 Flurl 实现这一点的理想方法是这样的,但目前这是不可能的:

await "http://random.org".WithProxy("http://myproxy").GetAsync();

我也知道为每个请求创建一个

FlurlClient
/
HttpClient
并不是一种选择,因为套接字耗尽问题,我过去也经历过这个问题。

这种情况的场景是,您需要拥有一个以某种方式轮换的代理池,以便每个 HTTP 请求都可能使用不同的代理 URL。

c# flurl
2个回答
12
投票

因此,在与 Flurl 创建者(#228#374)进行一些讨论后,我们提出的解决方案是使用自定义 FlurlClient 管理器类,该类将负责创建所需的

FlurlClient
以及链接的
HttpClient
实例。这是必需的,因为由于 .NET
FlurlClient
的设计方式的限制,每个
HttpClient
一次只能使用一个代理。

如果您正在寻找实际的解决方案(和代码),您可以跳到本答案的末尾。如果您想更好地理解,以下部分仍然有帮助。

[更新:我还构建了一个 HTTP 客户端库,它负责处理下面的所有内容,允许开箱即用地设置每个请求代理。它被称为PlainHttp。]

因此,第一个探索的想法是创建一个自定义

FlurlClientFactory
来实现
IFlurlClientFactory
接口。

工厂保留了一个

FlurlClient
池,当需要发送新请求时,会以
Url
作为输入参数来调用工厂。然后执行一些逻辑来决定请求是否应该通过代理。 URL 可能会被用作选择用于特定请求的代理的鉴别器。在我的例子中,将为每个请求选择一个随机代理,然后返回一个缓存的
FlurlClient

最终,工厂将创造:

  • 最多 每个代理 URL 一个
    FlurlClient
    (然后将用于必须通过该代理的所有请求);
  • 一组用于“正常”请求的客户端。
可以在

here找到此解决方案的一些代码。注册了定制工厂之后,就没有什么事情要做了。像await "http://random.org".GetAsync();

这样的标准请求将被
自动代理,如果工厂决定这样做。

不幸的是,这个解决方案有一个缺点。事实证明,在使用 Flurl 构建请求的过程中,

自定义工厂被多次调用。根据我的经验,被调用至少3次。这可能会导致问题,因为工厂可能不会针对相同的输入 URL 返回相同的

FlurlClient

解决方案

解决方案是构建一个自定义的

FlurlClientManager

 类,以完全绕过 FlurlClient 工厂机制并保留按需提供的自定义客户端池。

虽然此解决方案是专门为与出色的 Flurl 库配合使用而构建的,但可以直接使用

HttpClient

 类来完成非常类似的事情。

/// <summary> /// Static class that manages cached IFlurlClient instances /// </summary> public static class FlurlClientManager { /// <summary> /// Cache for the clients /// </summary> private static readonly ConcurrentDictionary<string, IFlurlClient> Clients = new ConcurrentDictionary<string, IFlurlClient>(); /// <summary> /// Gets a cached client for the host associated to the input URL /// </summary> /// <param name="url"><see cref="Url"/> or <see cref="string"/></param> /// <returns>A cached <see cref="FlurlClient"/> instance for the host</returns> public static IFlurlClient GetClient(Url url) { if (url == null) { throw new ArgumentNullException(nameof(url)); } return PerHostClientFromCache(url); } /// <summary> /// Gets a cached client with a proxy attached to it /// </summary> /// <returns>A cached <see cref="FlurlClient"/> instance with a proxy</returns> public static IFlurlClient GetProxiedClient() { string proxyUrl = ChooseProxy(); return ProxiedClientFromCache(proxyUrl); } private static string ChooseProxy() { // Do something and return a proxy URL return "http://myproxy"; } private static IFlurlClient PerHostClientFromCache(Url url) { return Clients.AddOrUpdate( key: url.ToUri().Host, addValueFactory: u => { return CreateClient(); }, updateValueFactory: (u, client) => { return client.IsDisposed ? CreateClient() : client; } ); } private static IFlurlClient ProxiedClientFromCache(string proxyUrl) { return Clients.AddOrUpdate( key: proxyUrl, addValueFactory: u => { return CreateProxiedClient(proxyUrl); }, updateValueFactory: (u, client) => { return client.IsDisposed ? CreateProxiedClient(proxyUrl) : client; } ); } private static IFlurlClient CreateProxiedClient(string proxyUrl) { HttpMessageHandler handler = new SocketsHttpHandler() { Proxy = new WebProxy(proxyUrl), UseProxy = true, PooledConnectionLifetime = TimeSpan.FromMinutes(10) }; HttpClient client = new HttpClient(handler); return new FlurlClient(client); } private static IFlurlClient CreateClient() { HttpMessageHandler handler = new SocketsHttpHandler() { PooledConnectionLifetime = TimeSpan.FromMinutes(10) }; HttpClient client = new HttpClient(handler); return new FlurlClient(client); } }
这个静态类保留了一个全局的 

FlurlClient

 池。与之前的解决方案一样,池包含:

  • 每个代理一个客户端
  • 每个主机一个客户端,用于所有不能通过代理的请求(这实际上是 Flurl 的默认工厂策略)。
在该类的实现中,代理由类本身选择(使用您想要的任何策略,例如循环或随机),但可以对其进行调整以将代理 URL 作为输入。在这种情况下,请记住,通过此实现,客户端在创建后永远不会被释放,因此您可能需要考虑这一点。

此实现还使用了自 .NET Core 2.1 起可用的新

SocketsHttpHandler.PooledConnectionLifetime

 选项,来解决当 
HttpClient
 实例具有较长生命周期时出现的 DNS 问题。在 .NET Framework 上,应使用 
ServicePoint.ConnectionLeaseTimeout
 属性。

使用管理器类很容易。对于正常请求,请使用:

await FlurlClientManager.GetClient(url).Request(url).GetAsync();
对于代理请求,请使用:

await FlurlClientManager.GetProxiedClient().Request(url).GetAsync();
    

0
投票
从 flurl 4 开始,不再需要编写大量样板代码,因为

IFlurlClientCache

 现在负责在命名客户端的基础上缓存 
IFlurlClient
 实例。现在创建代理客户端就像这样简单:

IFlurlClient flurlClient = flurlClientCache .GetOrAdd("CallBackWithProxyExample", "https://ineedaproxy.example.com", b => b.ConfigureInnerHandler(h => h.Proxy = new WebProxy(proxyServer)))
其他 

IFlurlClient

 实例可以使用不同的缓存键创建,不需要代理。

© www.soinside.com 2019 - 2024. All rights reserved.