如何为每个 url 使用 Polly 和 IHttpClientFactory

Posted

技术标签:

【中文标题】如何为每个 url 使用 Polly 和 IHttpClientFactory【英文标题】:How to use Polly and IHttpClientFactory per url 【发布时间】:2021-12-14 12:11:33 【问题描述】:

我正在尝试使用IHttpClientFactory.CreateClient([name]) 找到一种使用HttpClient 的方法,其中[name] 是使用Polly 在Startup 类中使用PolicyRegistry 配置的HttpClient。 我希望该策略适用于我使用该客户端进行调用的每个 url。

因此,如果我创建一个名为“myclient”的客户端,并且在整个应用程序中,我将使用该命名客户端调用 N 个不同的 url,我希望将策略单独应用于每个 url,而不是作为一个整体。

启动

PolicyRegistry registry = new PolicyRegistry();
AsyncPolicyWrap type1Policy = //somePolicy;
registry.Add("type1",type1Policy);
services.AddHttpClient("someClient").AddPolicyHandlerFromRegistry("type1");

一些消费者服务

public class SomeService

    private IHttpClientFactory factory;
    public SomeService(IHttpClientFactory factory)
    
        this.factory=factory;
    
    public void SomeCall(string url)
    
        var client=factory.GetClient("someClient");
        client.SendAsync(...);
    

在上述情况下,我希望为我正在调用的每个 url 单独采用启动类中设置的策略。

这可能吗?

【问题讨论】:

【参考方案1】:

为了实现这一点,您需要分别注册 HttpClient 和 PolicyRegistry:

services.AddHttpClient("someClient");
services.AddPolicyRegistry(registry);

然后您可以要求 DI 为您提供该注册表:

public class SomeService

    private readonly IHttpClientFactory factory;
    private readonly IReadOnlyPolicyRegistry<string> policyRegistry;

    public SomeService(IHttpClientFactory factory, IReadOnlyPolicyRegistry<string> policyRegistry)
    
        this.factory = factory;
        this.policyRegistry = policyRegistry;
    
    ...

最后在您的SomeCall 中,您可以检索策略:

public async Task SomeCall(string url)

     var client = factory.GetClient("someClient");
     var policy = policyRegistry.Get<IAsyncPolicy>("type1");

     await policy.ExecuteAsync(async ct => await client.SendAsync(..., ct), CancellationToken.None);


更新 #1:为每个网址创建新政策

在函数式编程中有一种众所周知的技术,称为Memoization。它基本上根据接收到的参数存储/缓存方法的输出。换句话说,如果该方法接收到相同的输入,那么它会从缓存中发出结果,而不是重新执行它。

这项技术适用于pure functions。如果它们以无副作用的方式接收完全相同的输入,则它们返回相同的值。

那么,如果我们能有一个解决方案

如果 url 是新的,则创建一个新策略 如果 url 是“已经看到”的,则返回缓存策略 那我们就可以走了。

这是一个简单的实现,但您可以找到很多变体,例如 1、2、3 等。

public interface IMemoizer<K, V> where K : IComparable

    Func<K, V> Memoize(Func<K, V> toBeMemoized);

public sealed class Memoizer<K, V> : IMemoizer<K, V> where K: IComparable

    public static readonly ConcurrentDictionary<K, Lazy<V>> memoizedValues;
    static Memoizer()
    
        memoizedValues = new ConcurrentDictionary<K, Lazy<V>>();
    

    public Func<K, V> Memoize(Func<K, V> toBeMemoized)
        => (K input) => memoizedValues.GetOrAdd(input, (K param) => new Lazy<V>(() => toBeMemoized(param))).Value;

如果你想拥有 TTL 那么你可以将ConcurrenctDictionary 替换为ASP.NET's MemoryCache

这是一个使用示例:

static void Main()

    var memoizer = new Memoizer<string, IAsyncPolicy<HttpResponseMessage>>();
    var policyMemoizer = memoizer.Memoize(GetSamplePolicy);

    var gPolicy1 = policyMemoizer("https://google.com");
    var soPolicy = policyMemoizer("https://***.com");
    var gPolicy2 = policyMemoizer("https://google.com");

    Console.WriteLine(gPolicy1 == gPolicy2); //true
    Console.WriteLine(gPolicy1 == soPolicy); //false
    
    var policyMemoizer2 = memoizer.Memoize(GetSamplePolicy);
    var gPolicy3 = policyMemoizer2("https://google.com");

    Console.WriteLine(gPolicy1 == gPolicy3); //true


static IAsyncPolicy<HttpResponseMessage> GetSamplePolicy(string _)
        => Policy<HttpResponseMessage>
        .Handle<HttpRequestException>()
        .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(2));

您可以将 memoizer 注册为单例,然后就可以使用它了。 :D

我希望它可以帮助您实现所需的行为。

【讨论】:

但是在这种情况下,不会有针对 type1 的全局策略吗?我的type1 政策不会汇总所有网址的失败吗?我不希望所有使用该类型策略的 url 都有一个策略实例。我想要每个 url 的策略实例。所以对于N url,我想要N 实例type1 策略。我不希望在所有 url 的策略的一个实例中汇总和计数失败。希望它清楚。 @BercoviciAdrian 哦,我明白了。你知道前面的网址吗?例如,您将这个 httpclient 用于少数下游服务。如果是这样,您可以将所有策略预先注册到注册表中,其中键可以是 url。如果你事先不知道,那就另当别论了。如果这是您的情况,请告诉我,明天我将修改我提出的解决方案。 好吧,目前这些 url 不是动态的,但在未来这是计划。场景如下:在我使用该注册表和 clientfactory 的服务中,我收到一个包含它应该发布到的 url 的请求。因此,我需要在我的服务中选择所需的策略和所需的 httpclient,但该策略应应用于每个唯一的 url。所以在我的服务中就像:“给我一个 X 类型的政策”,我将申请这个我收到的url 作为参数。 @BercoviciAdrian 我已经扩展了我的帖子,请查看。 @BercoviciAdrian Polly 尝试提供一个弹性库,其中策略声明及其应用程序是分开的。它试图足够通用以支持各种应用程序模型(如控制台、后台工作程序、webapi 等)。它试图提供可以组合以实现更复杂事物的原始元素。但是由于这些设计决策,它有一些限制,需要一些技巧/与其他工具组合来实现特定的目标。所以,简而言之,我不知道有任何内置解决方案:(

以上是关于如何为每个 url 使用 Polly 和 IHttpClientFactory的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security - OAuth2 和 CustomAuthenticationProvider。如何为每个配置不同的 URL 模式?

我将如何为每个主题创建 URL 模式?

如何为每个帖子自动制作带有新 URL 的子页面 [NodeJS]

在 HTMX 中,如何为选择表单元素中的每个选项请求特定的 URL?

如何为每个不同的feignclient定制不同的 “拦截器”

iOS 如何为每个 UIWebView 请求添加 cookie?