如何在Polly重试回调中获取HttpClient的完整URI?
我之前也碰到过这个问题,要在Polly的重试回调里拿到请求的完整URI,核心思路就是把请求URI传递到Polly的Context中——毕竟重试回调参数里正好有context,我们提前把URI存进去,回调时直接取出来就行。
下面分享两种可行的方法,推荐第二种,更通用省心:
方法一:手动在请求时传入Polly Context(适合少量请求场景)
如果只是个别请求需要记录重试URI,可以在调用HttpClient时手动创建并传入Polly Context,把URI存进去:
在你的JoinPackageApiClient请求方法里:
public async Task<YourResponseType> CallSomeEndpointAsync() { var requestPath = "/b/c?d=1"; // 拼接完整URI并存入Context var fullUri = new Uri(_client.BaseAddress, requestPath).ToString(); var pollyContext = new Context { ["RequestUri"] = fullUri }; // 调用HttpClient时传入Context var response = await _client.GetAsync(requestPath, pollyContext); // 后续处理逻辑... }
然后在GetRetryPolicy的onRetry回调里读取:
onRetry: (response, delay, retryCount, context) => { if (context.TryGetValue("RequestUri", out var uriObj) && uriObj is string requestUri) { // 这里就能拿到完整URI,比如https://a.com/b/c?d=1 Console.WriteLine($"正在重试第 {retryCount} 次,请求地址:{requestUri}"); } }
这种方法的缺点是每个需要重试的请求都要手动处理Context,不够优雅,适合请求量少的场景。
方法二:用自定义DelegatingHandler自动注入URI到Context(通用方案)
如果所有HttpClient请求都需要记录重试URI,推荐用这种方法——通过自定义消息处理程序,自动把请求的完整URI存入Polly Context,不用每个请求都手动处理。
步骤1:创建自定义DelegatingHandler
public class PollyContextUriInjector : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // 获取请求关联的Polly Context,没有就新建一个 var pollyContext = request.GetPolicyExecutionContext() ?? new Context(); // 把完整请求URI存入Context if (request.RequestUri != null) { pollyContext["RequestUri"] = request.RequestUri.ToString(); } // 把Context重新关联到请求上 request.SetPolicyExecutionContext(pollyContext); // 继续执行后续消息处理流程 return await base.SendAsync(request, cancellationToken); } }
步骤2:注册Handler到DI容器
在Startup/Program.cs里添加:
services.AddTransient<PollyContextUriInjector>();
步骤3:给HttpClient添加这个Handler
修改你的HttpClient注册代码:
services.AddHttpClient<JoinPackageApiClient>(jp => { jp.BaseAddress = new Uri(appSettings.JoinPackageWS.BaseUrl); }) .AddHttpMessageHandler<PollyContextUriInjector>() // 添加自定义Handler .AddPolicyHandler(GetRetryPolicy(appSettings, serviceProvider));
步骤4:在重试回调中读取URI
现在不管是响应状态码触发重试,还是超时、取消异常触发重试,都能从Context里稳定拿到完整URI:
onRetry: (response, delay, retryCount, context) => { if (context.TryGetValue("RequestUri", out var uriObj) && uriObj is string requestUri) { // 比如写入日志 _logger.LogWarning("重试请求:{RequestUri},第{RetryCount}次,延迟{DelaySeconds}秒", requestUri, retryCount, delay.TotalSeconds); } }
为什么不直接用response.RequestUri?
要注意:如果重试是因为异常(比如TimeoutRejectedException、TaskCanceledException)触发的,onRetry回调里的response参数会是null,这时候就无法通过response.RequestMessage?.RequestUri获取URI了。而用Context的方法不管是异常还是响应问题,都能稳定拿到URI,所以更可靠。
内容的提问来源于stack exchange,提问作者Royi Namir

