Polly重试策略中TimeoutRejectedException下无法获取RequestUri的解决方案
问题描述
在C#中使用Polly实现Http请求重试模式时,触发TimeoutRejectedException时,onRetryAsync方法接收的result.Result参数为null,超时异常仅存储在result.Exception中,但该异常不包含请求端点信息,导致日志无法填充requestUrl。
当前重试策略代码:
return Policy<HttpResponseMessage> .Handle<HttpRequestException>() .OrTransientHttpStatusCode() .OrAdditionalTransientHttpStatusCode() .Or<TimeoutRejectedException>() .WaitAndRetryAsync( retryCount: configuration.MaxRetries, sleepDurationProvider: (retryIndex, result, context) => { if (result.Result is { StatusCode: HttpStatusCode.ServiceUnavailable }) return some wait time; if (result.Result is { StatusCode: HttpStatusCode.TooManyRequests } || result.Result is { StatusCode: HttpStatusCode.Forbidden}) return some wait time; return some wait time; }, onRetryAsync: (result, span, retryIndex, context) => { string requestUrl = result.Result?.RequestMessage?.RequestUri?.AbsoluteUri ?? string.Empty; _logger.LogWarning("Transient type: {type} ; Retry index: {index}; Span: {seconds}; Uri: {uri}", type, retryIndex, span.TotalSeconds, requestUrl); return Task.CompletedTask; });
Startup中命名HttpClient的配置:
services .AddHttpClient("#id") .AddPolicyHandler((sp, _) => new PolicyFactory(sp.GetRequiredService<ILogger<PolicyFactory>>()).Create())
需求:通过配置策略获取请求Uri信息,且该策略可关联多个命名HttpClient,同时记录每个请求的详细信息。
解决方案
核心思路是利用**Polly执行上下文(Context)**传递请求Uri:在绑定策略到HttpClient时,将当前请求的Uri存入Context,之后在重试回调中从Context读取该信息,无论请求是否因超时失败都能获取到端点地址。
1. 修改HttpClient策略绑定逻辑
在AddPolicyHandler中,利用第二个参数(当前请求的HttpRequestMessage)将Uri注入到Polly Context中,确保每个请求的Uri都能被策略捕获:
services .AddHttpClient("#id") .AddPolicyHandler((sp, request) => { var policyFactory = sp.GetRequiredService<PolicyFactory>(); var retryPolicy = policyFactory.Create(); // 将请求Uri存入Polly执行上下文 return retryPolicy.WithPolicyContext(ctx => ctx["RequestUri"] = request.RequestUri?.AbsoluteUri); });
该方式支持多个命名HttpClient,每个请求的Uri都会被独立存入对应Context,无需修改策略的核心逻辑。
2. 调整重试回调中的日志逻辑
在onRetryAsync中,优先从Context读取Uri,同时兼容原有从result.Result获取的逻辑,确保所有异常场景都能正确记录请求地址:
onRetryAsync: (result, span, retryIndex, context) => { // 优先从Context取Uri,超时场景下result.Result为null,此时从Context获取 string requestUrl = context.TryGetValue("RequestUri", out var uriObj) ? uriObj.ToString() : result.Result?.RequestMessage?.RequestUri?.AbsoluteUri ?? string.Empty; // 统一获取异常/错误类型 string errorType = result.Exception != null ? result.Exception.GetType().Name : result.Result?.StatusCode.ToString() ?? "Unknown"; _logger.LogWarning("Transient type: {type} ; Retry index: {index}; Span: {seconds}; Uri: {uri}", errorType, retryIndex, span.TotalSeconds, requestUrl); return Task.CompletedTask; }
3. 可选:优化策略工厂(若需直接传递Uri)
如果希望策略工厂直接接收Uri参数,也可以修改PolicyFactory.Create方法,将Uri作为参数传入,然后在策略中通过闭包捕获:
// 修改PolicyFactory的Create方法 public Policy<HttpResponseMessage> Create(string requestUri) { return Policy<HttpResponseMessage> .Handle<HttpRequestException>() .OrTransientHttpStatusCode() .OrAdditionalTransientHttpStatusCode() .Or<TimeoutRejectedException>() .WaitAndRetryAsync( retryCount: _configuration.MaxRetries, sleepDurationProvider: /* 原有逻辑 */, onRetryAsync: (result, span, retryIndex, context) => { string requestUrl = requestUri ?? result.Result?.RequestMessage?.RequestUri?.AbsoluteUri ?? string.Empty; // 日志逻辑同前 return Task.CompletedTask; }); } // 对应Startup的配置 services .AddHttpClient("#id") .AddPolicyHandler((sp, request) => new PolicyFactory(sp.GetRequiredService<ILogger<PolicyFactory>>()) .Create(request.RequestUri?.AbsoluteUri));
内容的提问来源于stack exchange,提问作者CNL
相关产品推荐
相关产品推荐

