我知道,使用Flurl HTTP .NET库,我可以使用自定义HttpClientFactory
来设置全局代理,但是有一种方法可以为每个请求选择自定义代理< / strong>?
使用许多其他编程语言,设置代理就像设置选项一样容易。例如,使用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();
我也知道,由于socket exhaustion issue(我过去也经历过),因此无法为每个请求创建FlurlClient
/ HttpClient
。 / p>
这种情况的情况是,您需要一个以某种方式轮换的代理池,以便每个HTTP请求可能使用不同的代理URL。
答案 0 :(得分:1)
因此,在与Flurl创建者(#228和#374)进行了讨论之后,我们想到的解决方案是使用自定义的FlurlClient管理器类,该类负责创建所需的FlurlClient
和链接的HttpClient
实例。之所以需要这样做是因为每个FlurlClient
一次只能使用一个代理,这限制了.NET HttpClient
的设计方式。
如果您正在寻找实际的解决方案(和代码),则可以跳到该答案的结尾。如果您想更好地理解,以下部分仍然会有所帮助。
因此,第一个探索的想法是创建实现FlurlClientFactory
接口的自定义IFlurlClientFactory
。
工厂保留FlurlClient
的池,并且当需要发送新请求时,将工厂以Url
作为输入参数来调用。然后执行一些逻辑,以确定请求是否应通过代理。该URL可能用作选择用于特定请求的代理的区分符。在我的情况下,将为每个请求选择一个随机代理,然后返回缓存的FlurlClient
。
最后,工厂将创建:
FlurlClient
(然后将用于所有必须通过该代理的请求); 可以找到此解决方案的某些代码here。注册自定义工厂后,将无需执行其他任何操作。像await "http://random.org".GetAsync();
这样的标准请求会被自动代理,如果工厂决定这样做。
不幸的是,该解决方案有一个缺点。事实证明,在使用Flurl构建请求的过程中,自定义工厂被多次调用。根据我的经验,它称为at least 3 times。这可能会导致问题,因为工厂对于相同的输入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
s的全局池。与以前的解决方案一样,该池包括:
在该类的此实现中,代理由类本身选择(使用您想要的任何策略,例如循环或随机),但可以进行调整以将代理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();