2

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 ?

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 1
    I'm not sure if you have direct access to that anywhere, but one possibility would be to [add it to the context](https://github.com/App-vNext/Polly/wiki/Keys-And-Context-Data#using-context-for-custom-data) before you make the request so it will be available in `onRetry`. – Crowcoder Jul 30 '20 at 11:41
  • @Crowcoder will that context be shared among multiple `services.AddHttpClient` ? becuase I have many `services.AddHttpClient`'s and I don't want the context to be shared – Royi Namir Jul 30 '20 at 11:43
  • I'm not experienced with using it, but it looks like you can pass it in: `await Policy.ExecuteAsync(ctx => database.GetAsync(id), context);`. Not ideal, but it's an option. – Crowcoder Jul 30 '20 at 11:46
  • @RoyiNamir As I can see the `HttpClient` is injected by the DI, so on the consumer-side you are unaware of the fact that the client is decorated with your policy. On the policy definition side you are unaware of the fact who will be your consumer, a HttpClient, a RestClient, or anything else. Because of this loose coupling there is no built-in support for reaching the Url. – Peter Csala Jul 30 '20 at 14:58

1 Answers1

2

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75