我知道使用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。
因此,在与Flurl创建者(#228和#374)进行了讨论之后,我们想到的解决方案是使用自定义的FlurlClient管理器类,该类负责创建所需的FlurlClient
s和链接的HttpClient
实例。之所以需要这样做,是因为每个人FlurlClient
一次只能使用一个代理,这限制了.NETHttpClient
的设计方式。
如果您正在寻找实际的解决方案(和代码),则可以跳到该答案的结尾。如果您想更好地理解,以下部分仍然会有所帮助。
[更新:我还建立了一个HTTP客户端库,该库负责处理下面的所有内容,从而可以按需设置每个请求的代理。它称为PlainHttp。]
因此,第一个探索的想法是创建一个FlurlClientFactory
实现IFlurlClientFactory
接口的自定义。
工厂保留一个FlurlClient
s池,当需要发送新请求时,将工厂工厂Url
作为输入参数进行调用。然后执行一些逻辑,以确定请求是否应通过代理。该URL可能会用作选择用于特定请求的代理的区分符。在我的情况下,将为每个请求选择一个随机代理,然后FlurlClient
返回一个缓存。
最后,工厂将创建:
FlurlClient
(然后将用于必须通过该代理的所有请求);此解决方案的一些代码可以在此处找到。注册自定义工厂后,将无需执行其他任何操作。像标准的请求await "http://random.org".GetAsync();
将被自动的代理,如果工厂决定这样做。
不幸的是,该解决方案具有缺点。事实证明,在使用Flurl构建请求的过程中,多次调用了定制工厂。根据我的经验,它至少被调用了3次。这可能会导致问题,因为工厂FlurlClient
对于相同的输入URL可能不会返回相同的结果。
解决方案是构建自定义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作为输入。在这种情况下,请记住,使用此实现,客户端在创建之后就不会被丢弃,因此您可能需要考虑一下。
此实现还使用了SocketsHttpHandler.PooledConnectionLifetime
.NET Core 2.1以来提供的新选项来解决HttpClient
实例寿命较长时出现的DNS问题。在.NET Framework上,ServicePoint.ConnectionLeaseTimeout
应改为使用该属性。
使用manager类很容易。对于正常请求,请使用:
await FlurlClientManager.GetClient(url).Request(url).GetAsync();
对于代理请求,请使用:
await FlurlClientManager.GetProxiedClient().Request(url).GetAsync();
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句