You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 02:24:59