引发给定异常时无法让 Polly 重试 Http 调用

Ham*_*med 3 c# dependency-injection dotnet-httpclient polly httpclientfactory

我的服务定义:

var host = new HostBuilder().ConfigureServices(services =>
{
    services
        .AddHttpClient<Downloader>()
        .AddPolicyHandler((services, request) =>
            HttpPolicyExtensions
            .HandleTransientHttpError()
            .Or<SocketException>()
            .Or<HttpRequestException>()
            .WaitAndRetryAsync(
                new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10) },
                onRetry: (outcome, timespan, retryAttempt, context) =>
                {
                    Console.WriteLine($"Delaying {timespan}, retrying {retryAttempt}.");
                }));

    services.AddTransient<Downloader>();

}).Build();
Run Code Online (Sandbox Code Playgroud)

实施Downloader

class Downloader
{
    private HttpClient _client;
    public Downloader(IHttpClientFactory factory)
    {
        _client = factory.CreateClient();
    }

    public Download()
    {
        await _client.GetAsync(new Uri("localhost:8800")); // A port that no application is listening
    }
}
Run Code Online (Sandbox Code Playgroud)

通过此设置,我预计会看到三次尝试查询端点,并将日志消息打印到控制台(我也尝试使用记录器但未成功,为简单起见,这里使用控制台)。

我看到的是未处理的异常消息(我只希望在重试和打印日志后看到),而不是调试消息。

未处理的异常:System.Net.Http.HttpRequestException:无法建立连接,因为目标计算机主动拒绝它。(127.0.0.1:8800) ---> System.Net.Sockets.SocketException (10061): 无法建立连接,因为目标计算机主动拒绝连接。

Pet*_*ala 6

关于 HttpClient 类型的一些说明

您可以将几个不同的预配置注册HttpClient到 DI 系统中:

  • 命名客户端:它是一个命名的、预先配置的,可以通过的方法HttpClient访问IHttpClientFactoryCreate
  • 类型化客户端:它是一个预先配置的包装器HttpClient,可以通过ITypedHttpClientFactory或 通过包装器接口进行访问
  • 命名的、类型化的客户端:它是一个命名的、预配置的包装器HttpClient,可以通过IHttpClientFactory和访问它ITypedHttpClientFactory

扩展AddHttpClient方法

此方法尝试将工厂注册为单例,将具体类型注册为瞬态对象。在这里您可以找到相关的源代码。因此,您不需要自己将具体类型注册为 Transient 或 Scoped。

指定客户

AddHttpClient您可以通过提供唯一的名称来注册指定客户端

services.AddHttpClient("UniqueName", client => client.BaseAdress = ...);
Run Code Online (Sandbox Code Playgroud)

您可以通过以下方式访问注册客户端IHttpClientFactory

private readonly HttpClient uniqueClient;
public XYZService(IHttpClientFactory clientFactory)
  => uniqueClient = clientFactory.CreateClient("UniqueName");
Run Code Online (Sandbox Code Playgroud)

CreateClient打电话不带名字

如果您调用CreateClient没有名称的名称,它将创建一个HttpClient未预先配置的新名称。更准确地说,它不是由您预先配置的,而是由框架本身通过一些默认设置进行配置的。

这是您问题的根本原因,您创建了一个HttpClient未用策略修饰的对象。

类型化客户端

您可以通过AddHttpClient<TClient>或 通过AddHttpClient<TClient, TImplementation> 重载来注册类型化客户端

services.AddHttpClient<UniqueClient>(client => client.BaseAdress = ...);
services.AddHttpClient<IUniqueClient, UniqueClient>(client => client.BaseAdress = ...);
Run Code Online (Sandbox Code Playgroud)

前者可以通过ITypedHttpClientFactory

private readonly UniqueClient uniqueClient;
public XYZService(ITypedHttpClientFactory<UniqueClient> clientFactory)
  => uniqueClient = clientFactory.CreateClient(new HttpClient());
Run Code Online (Sandbox Code Playgroud)

后者可以通过类型化的客户端接口进行访问

private readonly IUniqueClient uniqueClient;
public XYZService(IUniqueClient client)
  => uniqueClient = client;
Run Code Online (Sandbox Code Playgroud)

两种情况下的实现类 ( UniqueClient) 都应该接收 anHttpClient作为参数

private readonly HttpClient httpClient;
public UniqueClient(HttpClient client)
  => httpClient = client;
Run Code Online (Sandbox Code Playgroud)

命名和类型化的客户端

正如您所看到的,我ITypedHttpClientFactory<UniqueClient>CreateClient新的HttpClient. (旁注:我也可以用 来称呼它clientFactory.CreateClient())。

但它不一定是默认值HttpClient。您也可以检索指定的客户端。在这种情况下,您将拥有一个命名的、类型化的客户端。

在这个 SO 主题中,我演示了如何使用此技术为不同的域多次注册相同的断路器修饰类型客户端。

  • 感谢您对此进行详细说明。 (2认同)