Search code examples
c#.net-coredotnet-httpclientpollyretry-logic

Get the full URI within a Polly retry attempt?


I'm using Polly to retry HttpClient attemnpts :

        services.AddHttpClient<JoinPackageApiClient>(jp => { jp.BaseAddress = new Uri(appSettings.JoinPackageWS.BaseUrl); })
            .AddPolicyHandler(GetRetryPolicy(appSettings, serviceProvider))

Where GetRetryPolicy is :

 private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(AppSettings appSettings, ServiceProvider serviceProvider)
        {
            return HttpPolicyExtensions
                .HandleTransientHttpError()
                .OrResult(msg => msg.StatusCode != HttpStatusCode.OK)
                .Or<TimeoutRejectedException>()
                .Or<TaskCanceledException>()
                .Or<OperationCanceledException>()

                .WaitAndRetryAsync(appSettings.PollySettings.RetryAttempts, (retryAttempt, c) =>
                {

                 return TimeSpan.FromSeconds(2);
                }, onRetry: (response, delay, retryCount, context) =>
                {

                   //█how can I access the full(!) HttpClient's URI here ?
                   //e.g. : https://a.com/b/c?d=1
               
                });
        }

Question:

Looking at the onRetry parameter, I want to log the full URL attempt in the onRetry section.

How can I get the full URI in that section ?


Solution

  • I was wrong when I have stated in my comment that this is not possible.

    enter image description here

    I have to correct myself: Yes, it is doable and it is actually pretty easy. :)

    All you need to do is to use a different overload of AddPolicyHandler

    public static IHttpClientBuilder AddPolicyHandler (this IHttpClientBuilder builder, Func<IServiceProvider,HttpRequestMessage,IAsyncPolicy<HttpResponseMessage>> policySelector);
    

    So, whenever you call the AddPolicyHandler you have to provide a function, which

    • receives an IServiceProvider instance to be able to access to any DI registered service
    • also receives the request on which you can access the url via the RequestUri property
    • and returns an IAsyncPolicy<HttpResponseMessage>

    So, you have to modify the registration code like this:

    services.AddHttpClient<JoinPackageApiClient>(jp => ...)
            .AddPolicyHandler((provider, request) => GetRetryPolicy(appSettings, provider, request));
    

    and the GetRetryPolicy method like this:

    private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(
        AppSettings appSettings,
        IServiceProvider serviceProvider, 
        HttpRequestMessage request)
       => HttpPolicyExtensions
            .HandleTransientHttpError()
            ...
            .WaitAndRetryAsync(appSettings.PollySettings.RetryAttempts, 
            (_, __) => TimeSpan.FromSeconds(2),
            (response, delay, retryCount, context) =>
            {
                var url = request.RequestUri;
                // ...
            });
    

    The good news is that the url will change whenever you issue a new request. (In my test client I have set the BaseAddress to http://httpstat.us) When I run this test

    await client.GetAsync("/200");
    try
    {
        await client.GetAsync("/401");
    }
    catch { }
    await client.GetAsync("/403");
    

    then the retry triggered 4 times (2x for 401 and 2x for 403). The onRetry received the related HttpRequestMessage so, the logging was accurate.