使用.NET Flurl / HttpClient设置每个请求的代理(或旋转代理)

mcont

我知道使用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。

mcont

因此,在与Flurl创建者(#228#374进行了讨论之后,我们想到的解决方案是使用自定义的FlurlClient管理器类,该类负责创建所需的FlurlClients和链接的HttpClient实例。之所以需要这样做,是因为每个人FlurlClient一次只能使用一个代理,这限制了.NETHttpClient的设计方式。

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

[更新:我还建立了一个HTTP客户端库,该库负责处理下面的所有内容,从而可以按需设置每个请求的代理。它称为PlainHttp。]

因此,第一个探索的想法是创建一个FlurlClientFactory实现IFlurlClientFactory接口的自定义

工厂保留一个FlurlClients,当需要发送新请求时,将工厂工厂Url作为输入参数进行调用然后执行一些逻辑,以确定请求是否应通过代理。该URL可能会用作选择用于特定请求的代理的区分符。在我的情况下,将为每个请求选择一个随机代理,然后FlurlClient返回一个缓存

最后,工厂将创建:

  • 每个代理URL最多一个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);
    }
}

该静态类保留FlurlClients的全局池与先前的解决方案一样,该池包括:

  • 每个代理一个客户;
  • 每个主机一个客户端,用于所有不能通过代理的请求(这实际上是Flurl的默认出厂策略)。

在该类的实现中,代理由类本身选择(使用您想要的任何策略,例如循环或随机),但可以进行修改以将代理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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章