我有一个应用程序,它请求通过身份验证的服务,因此需要传递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
答案 0 :(得分:2)
TL; DR:AddPolicyHandler
和AddHttpMessageHandler
的顺序确实很重要。
我已经用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();
}
}
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;
}
}
}
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);
});
TestController
的{{1}} Get
的{{1}} TestClient
的{{1}}的{{1}} Get
的{{1}} TestHandler
的{{1}}的{{1}} SendAsync
的{{1}}失败,try
(内部:RetryPolicy
)因此,此处不会触发重试策略。
onRetry
TestHandler
的{{1}} SendAsync
的{{1}} catch
的{{1}}的{{1}} TestController
的{{1}}的{{1}} Get
的{{1}} HttpRequestException
的{{1}}的{{1}} SocketException
的{{1}}的{{1}} 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}}失败,出现TestController
(内部:Get
)因此,此处的重试策略已被触发。