Flurl.Http 如何使用 Polly?
How to use Polly with Flurl.Http?
目前我有这个请求:
await url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>()
我想立即开始使用 Polly (https://github.com/App-vNext/Polly) 来处理重试并提供更好的用户体验。例如,由于网络连接不良,第一次尝试时用户不会 "hanging up" 。这是我尝试使用的示例:
int[] httpStatusCodesWorthRetrying = { 408, 500, 502, 503, 504 };
Policy
.Handle<HttpException>()
.OrResult<HttpResponse>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
})
.ExecuteAsync( await url... )
但是需要HttpResponse
是return类型。从我的 Flurl 示例中可以看出,它是 returning T
,即使它是 HttpResponse
。 T
只是用于反序列化 StringContent
的类型。
第一个示例根本不起作用,因为我在 PCL 中使用它并且我无法在那里获得对 System.Web
的引用。所以我尝试了这个:
Policy
.HandleResult(HttpStatusCode.InternalServerError)
.OrResult(HttpStatusCode.BadGateway)
.OrResult(HttpStatusCode.BadRequest)
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
})
.ExecuteAsync(async () =>
{
await url...
});
但是这个也不起作用,因为 Polly 期望 HttpStatusCode
作为 return 类型。所以我的问题是:如何告诉 polly 处理那些 HttpStatusCode
并仍然允许我的 return 类型 T
?
Polly 可以将通过策略执行的委托返回的任何值解释为错误。但是,正如您所观察到的,在您发布的示例中对 .GetJsonAsync<T>()
的调用:
await url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>()
正在返回 T
。该调用通过直接将 Json 反序列化为 T
来隐藏 HttpResponseMessage
。
您需要在 returns HttpResponseMessage
左右的 flurl 中使用重载。我没有使用过 flurl,但 this overload 返回 Task<HttpResponseMessage>
看起来很有希望。你可能会做这样的事情:
List<int> httpStatusCodesWorthRetrying = new List<int>(new[] {408, 500, 502, 503, 504});
HttpResponseMessage response = await Policy
.Handle<HttpRequestException>()
.Or<OtherExceptions>() // add other exceptions if you find your call may throw them, eg FlurlHttpException
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains((int)r.StatusCode))
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
})
.ExecuteAsync(() =>
url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetAsync()
);
T responseAsT = await Task.FromResult(response).ReceiveJson<T>();
建议最后调用 .ReceiveJson<T>()
只是比较原始调用的 flurl 源代码 .GetJsonAsync<T>()
here with the substituted .GetAsync();
here.
当然,您可以将其全部包装到 flurl 上的简洁扩展辅助方法中,也许是这样的:
async T GetJsonAsyncResiliently<T>(this IFlurlClient client, Policy policy) // OR (if preferred): this Url url instead of IFlurlClient client
{
return await Task.FromResult(policy.ExecuteAsync(() => client.GetAsync())).ReceiveJson<T>();
}
编辑:我可能在指向 IFlurlClient
上的方法时针对您的情况指出了错误的 flurl 重载。但是,Url
和 string
上的 flurl 中存在一组平行的扩展方法,因此适用相同的原则。
您不需要中断使用像 GetJsonAsync<T>()
这样的便捷方法,因为 Flurl 会在非 2XX 响应(或者您 configure 它)上抛出异常,这应该允许它和 Polly 玩得很好。只需删除原始代码中的 .Handle<HttpException>
和 .OrResult<HttpResponse>
部分并改为处理 FlurlHttpException
:
T poco = await Policy
.Handle<FlurlHttpException>(ex => httpStatusCodesWorthRetrying.Contains((int)ex.Call.Response.StatusCode))
.WaitAndRetryAsync(...)
.ExecuteAsync(() => url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>());
还有进一步清理的建议:
T poco = await Policy
.Handle<FlurlHttpException>(IsWorthRetrying)
.WaitAndRetryAsync(...)
.ExecuteAsync(() => url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>());
private bool IsWorthRetrying(FlurlHttpException ex) {
switch ((int)ex.Call.Response.StatusCode) {
case 408:
case 500:
case 502:
case 504:
return true;
default:
return false;
}
}
通过设置可以使用 Polly 配置的 HttpClientFactory
配置 Flurl 并创建自定义 HttpClientFactory
:
public class MyCustomHttpClientFactory : DefaultHttpClientFactory, IMyCustomHttpClientFactory
{
private readonly HttpClient _httpClient;
public MyCustomHttpClientFactory(HttpClient httpClient)
{
_httpClient = httpClient;
}
public override HttpClient CreateHttpClient(HttpMessageHandler handler)
{
return _httpClient;
}
}
在 ConfigureServices
中注册该服务:
public void ConfigureServices(IServiceCollection services)
{
services
.AddHttpClient<IMyCustomHttpClientFactory, MyCustomHttpClientFactory>()
.SetHandlerLifetime(...)
.AddPolicyHandler(....);
}
然后将该工厂分配给 Flurl:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Get HttpClientFactory and Configure Flurl to use it.
var factory = (IMyCustomHttpClientFactory)app.ApplicationServices.GetService(typeof(IMyCustomHttpClientFactory));
FlurlHttp.Configure((settings) => settings.HttpClientFactory = factory);
}
目前我有这个请求:
await url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>()
我想立即开始使用 Polly (https://github.com/App-vNext/Polly) 来处理重试并提供更好的用户体验。例如,由于网络连接不良,第一次尝试时用户不会 "hanging up" 。这是我尝试使用的示例:
int[] httpStatusCodesWorthRetrying = { 408, 500, 502, 503, 504 };
Policy
.Handle<HttpException>()
.OrResult<HttpResponse>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
})
.ExecuteAsync( await url... )
但是需要HttpResponse
是return类型。从我的 Flurl 示例中可以看出,它是 returning T
,即使它是 HttpResponse
。 T
只是用于反序列化 StringContent
的类型。
第一个示例根本不起作用,因为我在 PCL 中使用它并且我无法在那里获得对 System.Web
的引用。所以我尝试了这个:
Policy
.HandleResult(HttpStatusCode.InternalServerError)
.OrResult(HttpStatusCode.BadGateway)
.OrResult(HttpStatusCode.BadRequest)
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
})
.ExecuteAsync(async () =>
{
await url...
});
但是这个也不起作用,因为 Polly 期望 HttpStatusCode
作为 return 类型。所以我的问题是:如何告诉 polly 处理那些 HttpStatusCode
并仍然允许我的 return 类型 T
?
Polly 可以将通过策略执行的委托返回的任何值解释为错误。但是,正如您所观察到的,在您发布的示例中对 .GetJsonAsync<T>()
的调用:
await url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>()
正在返回 T
。该调用通过直接将 Json 反序列化为 T
来隐藏 HttpResponseMessage
。
您需要在 returns HttpResponseMessage
左右的 flurl 中使用重载。我没有使用过 flurl,但 this overload 返回 Task<HttpResponseMessage>
看起来很有希望。你可能会做这样的事情:
List<int> httpStatusCodesWorthRetrying = new List<int>(new[] {408, 500, 502, 503, 504});
HttpResponseMessage response = await Policy
.Handle<HttpRequestException>()
.Or<OtherExceptions>() // add other exceptions if you find your call may throw them, eg FlurlHttpException
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains((int)r.StatusCode))
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
})
.ExecuteAsync(() =>
url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetAsync()
);
T responseAsT = await Task.FromResult(response).ReceiveJson<T>();
建议最后调用 .ReceiveJson<T>()
只是比较原始调用的 flurl 源代码 .GetJsonAsync<T>()
here with the substituted .GetAsync();
here.
当然,您可以将其全部包装到 flurl 上的简洁扩展辅助方法中,也许是这样的:
async T GetJsonAsyncResiliently<T>(this IFlurlClient client, Policy policy) // OR (if preferred): this Url url instead of IFlurlClient client
{
return await Task.FromResult(policy.ExecuteAsync(() => client.GetAsync())).ReceiveJson<T>();
}
编辑:我可能在指向 IFlurlClient
上的方法时针对您的情况指出了错误的 flurl 重载。但是,Url
和 string
上的 flurl 中存在一组平行的扩展方法,因此适用相同的原则。
您不需要中断使用像 GetJsonAsync<T>()
这样的便捷方法,因为 Flurl 会在非 2XX 响应(或者您 configure 它)上抛出异常,这应该允许它和 Polly 玩得很好。只需删除原始代码中的 .Handle<HttpException>
和 .OrResult<HttpResponse>
部分并改为处理 FlurlHttpException
:
T poco = await Policy
.Handle<FlurlHttpException>(ex => httpStatusCodesWorthRetrying.Contains((int)ex.Call.Response.StatusCode))
.WaitAndRetryAsync(...)
.ExecuteAsync(() => url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>());
还有进一步清理的建议:
T poco = await Policy
.Handle<FlurlHttpException>(IsWorthRetrying)
.WaitAndRetryAsync(...)
.ExecuteAsync(() => url
.SetQueryParams(queryString)
.SetClaimsToken()
.GetJsonAsync<T>());
private bool IsWorthRetrying(FlurlHttpException ex) {
switch ((int)ex.Call.Response.StatusCode) {
case 408:
case 500:
case 502:
case 504:
return true;
default:
return false;
}
}
通过设置可以使用 Polly 配置的 HttpClientFactory
配置 Flurl 并创建自定义 HttpClientFactory
:
public class MyCustomHttpClientFactory : DefaultHttpClientFactory, IMyCustomHttpClientFactory
{
private readonly HttpClient _httpClient;
public MyCustomHttpClientFactory(HttpClient httpClient)
{
_httpClient = httpClient;
}
public override HttpClient CreateHttpClient(HttpMessageHandler handler)
{
return _httpClient;
}
}
在 ConfigureServices
中注册该服务:
public void ConfigureServices(IServiceCollection services)
{
services
.AddHttpClient<IMyCustomHttpClientFactory, MyCustomHttpClientFactory>()
.SetHandlerLifetime(...)
.AddPolicyHandler(....);
}
然后将该工厂分配给 Flurl:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Get HttpClientFactory and Configure Flurl to use it.
var factory = (IMyCustomHttpClientFactory)app.ApplicationServices.GetService(typeof(IMyCustomHttpClientFactory));
FlurlHttp.Configure((settings) => settings.HttpClientFactory = factory);
}