Polly HandleTransientHttpError not catching HttpRequestExcep



我已经在启动时创建了一个重试策略。ConfigureServices方法。还请注意,默认情况下,asp.net core 2.1记录4[信息]行,用于HttpClient的每次调用,这些都显示在我的问题末尾的日志中。

services.AddHttpClient("ResilientClient")
.AddPolicyHandler(
Policy.WrapAsync(
PollyRetryPolicies.TransientErrorRetryPolicy(),
Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(60))));

策略定义如下。请注意,我将重试尝试写入日志,因此我将知道重试策略是否被调用。

public static IAsyncPolicy < HttpResponseMessage > TransientErrorRetryPolicy() {
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or < TimeoutRejectedException > ()
.WaitAndRetryAsync(sleepDurations: ExponentialBackoffPolicy.DecorrelatedJitter(3, SEED_DELAY, MAX_DELAY),
onRetry: (message, timespan, attempt, context) => {
context.GetLogger() ? .LogInformation($ "Retrying request to {message?.Result?.RequestMessage?.RequestUri} in {timespan.TotalSeconds} seconds. Retry attempt {attempt}.");
});
}

HandleTransientHttpError()是一个Polly扩展,它在注释中声明:

配置处理的条件有:•网络故障(如System.Net.Http.HttpRequestException)

我的httpclient使用是这样的:

using (HttpResponseMessage response = await _httpClient.SendAsync(request)) 
{
response.EnsureSuccessStatusCode();
try 
{
string result = await response.Content.ReadAsStringAsync();
if (result == null || result.Trim().Length == 0) {
result = "[]";
}
return JArray.Parse(result);
} catch (Exception ex) {
_logger.LogInformation($ "Failed to read response from {url}. {ex.GetType()}:{ex.Message}");
throw new ActivityException($ "Failed to read response from {url}.", ex);
}
}

捕获以下日志:

[Information] System.Net.Http.HttpClient.ResilientClient.LogicalHandler: Start processing HTTP request GET https://api.au.... obfuscated
[Information] System.Net.Http.HttpClient.ResilientClient.CustomClientHandler: Sending HTTP request GET https://api.au..... obfuscated
[Information] System.Net.Http.HttpClient.ResilientClient.CustomClientHandler: Received HTTP response after 2421.8895ms - 200
[Information] System.Net.Http.HttpClient.ResilientClient.LogicalHandler: End processing HTTP request after 2422.1636ms - OK

Unknown error responding to request: HttpRequestException:
System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: The server returned an invalid or unrecognized response.
at System.Net.Http.HttpConnection.FillAsync()
at System.Net.Http.HttpConnection.ChunkedEncodingReadStream.CopyToAsyncCore(Stream destination, CancellationToken cancellationToken)
at System.Net.Http.HttpConnection.HttpConnectionResponseContent.SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken)
at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer)
--- End of inner exception stack trace ---
at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer)
at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at nd_activity_service.Controllers.ActivityController.GetND(String url) in /codebuild/output/src251819872/src/src/nd-activity-service/Controllers/ActivityController.cs:line 561

Http调用成功,我可以看到它返回200 - OK。然后抛出HttpRequestException。我假设没有调用策略,因为HttpClient消息管道已经解析,我们可以看到它返回200 - OK。那么它是如何抛出异常的呢?

我该怎么处理呢?围绕处理HttpRequestExceptions的方法包装另一个策略?

这个错误似乎是暂时的。它是一个已调度的作业,并在下次调用时工作。

您的策略是针对HttpClient而不是针对HttpResponseMessage定义的。

因此,response.EnsureSuccessStatusCode()而不是触发重试,即使您收到例如428.

如果您从下游系统接收到408或5XX状态码,HandleTransientHttpError将触发重试。当SendAsync抛出HttpRequestException


因为你的异常StackTrace看起来像这样:

System.Net.Http。HttpRequestException:复制内容到流时出错。

先。IOException:服务器返回了一个无效的或未被回应。

这就是为什么我有根据的猜测是,当您尝试读取响应体(ReadAsStringAsync)时,这个异常是由HttpContent类抛出的。

这将触发重试,因为您已经在HttpClient上定义了策略。


如果你想在这些情况下重试,当response.EnsureSuccessStatusCode()抛出HRE或response.Content.ReadAsStringAsync()抛出HRE时,那么你必须将整个http通信和响应处理逻辑包装到重试策略中。

让我告诉你怎么做。

首先使用PolicyRegistry代替AddPolicyHandler:

//services.AddHttpClient("ResilientClient")
//    .AddPolicyHandler(
//        Policy.WrapAsync(
//            TransientErrorRetryPolicy(),
//            Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(60))));
services.AddHttpClient("ResilientClient");
var registry = services.AddPolicyRegistry();
registry.Add("retry", Policy.WrapAsync(
TransientErrorRetryPolicy(),
Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(60))));

然后向DI索要寄存器,例如:

private readonly IHttpClientFactory factory;
private readonly IReadOnlyPolicyRegistry<string> registry;
public TestController(IHttpClientFactory factory, IReadOnlyPolicyRegistry<string> registry)
{
this.factory = factory;
this.registry = registry;
}

最后检索组合策略并执行http调用:

var retryPolicy = registry.Get<IAsyncPolicy<HttpResponseMessage>>("retry");
await retryPolicy.ExecuteAsync(async () => await IssueRequest());
private async Task<HttpResponseMessage> IssueRequest()
{
var _httpClient = factory.CreateClient("ResilientClient");
HttpResponseMessage response = await _httpClient.GetAsync("http://httpstat.us/428");
response.EnsureSuccessStatusCode();
return response;
}

我已经使用了httpstat。我们模拟428响应。

相关内容

  • 没有找到相关文章

最新更新