我在HttpClient方法中为我的Startup.ConfigureServices创建了一个重试策略。还请注意,默认情况下,asp.net Core2.1为HttpClient发出的每个调用记录4行信息行,这些信息行显示在我问题末尾的日志中。
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的方法包装另一个策略?
这个错误似乎是暂时的。这是一个计划好的作业,下次调用时可以工作。
发布于 2021-10-27 23:41:26
您的策略是针对HttpClient
而不是针对HttpResponseMessage
定义的。
因此,即使您收到例如428,response.EnsureSuccessStatusCode()
也将而不是触发器重试。
如果从下游系统接收到408或5XX状态代码,则HandleTransientHttpError
将触发重试。当SendAsync
抛出HttpRequestException
时
因为您的异常StackTrace如下所示:
System.Net.Http.HttpRequestException:将内容复制到流中时出错。 System.IO.IOException:服务器返回无效或无法识别的响应。
这就是为什么我的猜测是,这个异常是由HttpContent
类抛出的,而您试图读取响应体(ReadAsStringAsync
)。
这将不会触发重试,因为您已经在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.us来模拟428个响应。
https://stackoverflow.com/questions/69749167
复制相似问题