5

我有一个应用程序请求经过身份验证的服务,需要通过access_token.

我的想法是在access_token过期时使用 Polly 重试。

我在 .NET Core 3.1 应用程序中使用 Refit (v5.1.67) 和 Polly (v7.2.1)。

服务注册如下:

services.AddTransient<ExampleDelegatingHandler>();

IAsyncPolicy<HttpResponseMessage> retryPolicy = Policy<HttpResponseMessage>
    .Handle<ApiException>()
    .RetryAsync(1, (response, retryCount) =>
    {
        System.Diagnostics.Debug.WriteLine($"Polly Retry => Count: {retryCount}");
    });

services.AddRefitClient<TwitterApi>()
    .ConfigureHttpClient(c =>
    {
        c.BaseAddress = new Uri("https://api.twitter.com/");
    })
    .AddHttpMessageHandler<ExampleDelegatingHandler>()
    .AddPolicyHandler((sp, req) =>
    {
        //this policy does not works, because the exception is not catched on 
        //"Microsoft.Extensions.Http.PolicyHttpMessageHandler" (DelegatingHandler)
        return retryPolicy;
    });
public interface TwitterApi
{
    [Get("/2/users")]
    Task<string> GetUsers();
}
public class ExampleDelegatingHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            return await base.SendAsync(request, cancellationToken);
        }
        catch (Exception)
        {
            //Why do not catch the exception?
            throw;
        }
    }
}

重试策略不起作用!

分析问题,我意识到异常没有被 HttpClient 的DelegatingHandler. 由于该AddPolicyHandler语句正在生成一个DelegatingHandler( PolicyHttpMessageHandler) 来执行该策略,并且没有在此处捕获异常,因此该策略永远不会执行。我意识到这个问题只发生在可以发送请求的异步场景中。在同步场景中它可以工作(例如:超时)。

为什么内部没有捕获异常DelegatingHandler

我附上了一个模拟 Twitter 调用的示例项目。

https://www.dropbox.com/s/q1797rq1pbjvcls/ConsoleApp2.zip?dl=0

外部参考:

https://github.com/reactiveui/refit#using-httpclientfactory

https://www.hanselman.com/blog/UsingASPNETCore21sHttpClientFactoryWithRefitsRESTLibrary.aspx

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1

4

2 回答 2

10

我遇到了一个涉及 .NET 5 >= 与 Polly 和 HttpClient 的问题,编译器显示:HttpClientBuilder 不包含 AddPolicyHandler 的定义。当我将 Nuget Package 更改为 时,我可以修复它Polly.Extensions.HttpMicrosoft.Extensions.Http.Polly我知道这与此处报告的情况不同,但它可能对像我这样来这里寻找此答案的其他人有用。

于 2021-11-16T16:53:41.227 回答
7

AddPolicyHandlerTL;DR:和的顺序AddHttpMessageHandler很重要。


我已经用HttpClient(所以没有改装)重现了这个问题。

用于测试的类型化 HttpClient

public interface ITestClient
{
    Task<string> Get();
}

public class TestClient: ITestClient
{
    private readonly HttpClient client;
    public TestClient(HttpClient client)
    {
        this.client = client;
    }
    public async Task<string> Get()
    {
        var resp = await client.GetAsync("http://not-existing.site");
        return "Finished";
    }
}

测试用控制器

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    private readonly ITestClient client;

    public TestController(ITestClient client)
    {
        this.client = client;
    }

    [HttpGet]
    public async Task<string> Get()
    {
        return await client.Get();
    }
}

用于测试的 DelegateHandler

public class TestHandler: DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            return await base.SendAsync(request, cancellationToken);
        }
        catch (System.Exception ex)
        {
            _ = ex;
            throw;
        }
    }
}

订购 #1 - 处理程序,策略

启动

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddTransient<TestHandler>();
    services.AddHttpClient<ITestClient, TestClient>()
        .AddHttpMessageHandler<TestHandler>() //Handler first
        .AddPolicyHandler(RetryPolicy()); //Policy second
}

private IAsyncPolicy<HttpResponseMessage> RetryPolicy()
    => Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .RetryAsync(1, (resp, count) =>
    {
        Console.WriteLine(resp.Exception);
    });

执行顺序

  1. TestControllerGet
  2. TestClientGet
  3. TestHandlerSendAsync_try
  4. RetryPolicyonRetry
  5. TestHandlerSendAsync_catch
  6. TestControllerGet失败HttpRequestException(内部SocketException:)

所以,这里没有触发重试策略。

订购 #2 - 政策、处理程序

启动

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddTransient<TestHandler>();
    services.AddHttpClient<ITestClient, TestClient>()
        .AddPolicyHandler(RetryPolicy()) //Policy first
        .AddHttpMessageHandler<TestHandler>(); //Handler second
}

private IAsyncPolicy<HttpResponseMessage> RetryPolicy()
    => Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .RetryAsync(1, (resp, count) =>
    {
        Console.WriteLine(resp.Exception);
    });

执行顺序

  1. TestControllerGet
  2. TestClientGet
  3. TestHandlerSendAsync_try
  4. TestHandlerSendAsync_catch
  5. RetryPolicyonRetry
  6. TestHandlerSendAsync_try
  7. TestHandlerSendAsync_catch
  8. TestControllerGet失败HttpRequestException(内部SocketException:)

所以,这里重试策略被触发了。

于 2020-09-02T07:38:58.100 回答