Polly策略不起作用,使用";AddPolicyHandler"



我有一个应用程序,它请求经过身份验证的服务,其中需要传递access_token

如果access_token过期,我的想法是使用Polly重试。

我在.NET Core 3.1应用程序中使用重新安装(v.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://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1

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

TL;DR:AddPolicyHandlerAddHttpMessageHandler的排序确实很重要。


我重新创建了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. TestHandlerSendAsynctry
  4. RetryPolicyonRetry
  5. TestHandlerSendAsynccatch
  6. TestControllerGetHttpRequestException一起失败(内部: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. TestHandlerSendAsynctry
  4. TestHandlerSendAsynccatch
  5. RetryPolicyonRetry
  6. TestHandlerSendAsynctry
  7. TestHandlerSendAsynccatch
  8. TestControllerGetHttpRequestException一起失败(内部:SocketException)

因此,此处已启动重试策略。

1。为什么

在执行策略和委派处理程序时,失败的HTTP响应还不是例外。它只是HttpResponseMessage的一个不成功的实例。重新安装将此状态转换为异常,作为请求-响应处理的最后一步。

2.订单

正如Peter Csala的回答中正确指出的那样,秩序很重要。提出请求时:

  1. 重新装配将参数串行化为HttpRequestMessage并将其传递给HttpClient
  2. HttpClient做前期准备
  3. HttpClient按照将请求消息添加到客户端的顺序,通过处理程序和策略运行请求消息
  4. 将生成的消息发送到服务器
  5. 服务器的响应被转换为HttpResponseMessage对象
  6. 该对象通过相同的处理程序和策略序列冒泡,但顺序相反
  7. HttpClient进行最终处理并将结果返回给"重新装配">
  8. 重新安装将任何错误转换为ApiExceptions

因此,重试策略将重新运行之后添加的所有内容,但之前的内容将只执行一次。

因此,如果您希望在每次重试时重新生成access_token,则创建令牌的委派处理程序必须在重试策略之后注册

3.如何

重试HTTP失败的最简单方法是使用Polly.Extensions.Http中的HttpPolicyExtensions.HandleTransientHttpError()。否则,您将不得不自己检查所有失败的HTTP状态代码。HandleTransientHttpError的好处是,它只在有意义的失败时重试,比如500或套接字错误。另一方面,它不会重试404,例如,因为资源不在那里,如果我们重试,就不太可能再次出现。

我认为如果我们更改策略

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

.HandleResult(x => !x.IsSuccessStatusCode)

.HandleResult(x => _retryStatusCodes.Contains(x.StatusCode))
...
private static readonly ISet<HttpStatusCode> _retryStatusCodes = new HashSet<HttpStatusCode>
{
HttpStatusCode.RequestTimeout,
HttpStatusCode.BadGateway,
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.GatewayTimeout,
};

那么它应该起作用。

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

也许重新安装会检查状态代码,并在稍后阶段的上抛出ApiException

相关内容

  • 没有找到相关文章

最新更新