简体   繁体   中英

How to use HttpClient & Polly to have a Retry or Fallback Policy to retry with a different URL?

I'm new to Polly, but want to implement it as it seems like a good option for handling exponential backoff if an HTTP request fails.

What I'd like to occur is that it tries using the original URL and if that request fails, it tries again but with the URL manipulated so that it's routed through a proxy service.

So, for example, the original request would have:

var requestUrl = "https://requestedurl.com";

If that fails, the request will be retried in a different format, such as:

requestUrl = $"https://proxy.com?url={requestUrl}";

Ideally, I would be able to provide a list of proxy URLs that it would retry. So, if the above example failed, it would move onto the next:

requestUrl = $"https://proxy2.com?url={requestUrl}";

I'm not sure if this should be in the RetryPolicy or as a FallbackPolicy.

For completeness, this is what I have thus far:

In a helper class, I have two methods:

public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 5);

    return Policy
        .Handle<HttpRequestException>()
        .Or<TimeoutRejectedException>()
        .OrTransientHttpError()
        .Or<BrokenCircuitException>()
        .Or<OperationCanceledException>()
        .OrInner<OperationCanceledException>()
        .WaitAndRetryAsync(delay);
}

public static IAsyncPolicy<HttpResponseMessage> GetTimeoutPolicy()
{
    return Policy.TimeoutAsync<HttpResponseMessage>(10);
}

In my Startup.cs, I have the following (this is for an Azure Function by the way):

builder.Services.AddHttpClient<IRssQueueRequestService, RssQueueRequestService>()
    .AddPolicyHandler(HttpClientHelpers.GetRetryPolicy())
    .AddPolicyHandler(HttpClientHelpers.GetTimeoutPolicy())
    .SetHandlerLifetime(TimeSpan.FromMinutes(10));

As mentioned, I haven't used Polly before. Typically I'd just have the first line to insatiate the HTTP client service, so if there's anything I'm doing wrong, I'd appreciate the feedback.

The Question is... Going back the beginning, how to retry a request but with a different URL?

The retry by definition tries to execute the same operation.
So, by default it is not supported to have different urls for different attempts.

There are several workaround on the other hand.
Let me present two where we are using an url enumerator.

Option 1 - Use the url enumerator directly

IEnumerable<string> GetAddresses()
{
    yield return "https://requestedurl.com";
    yield return "https://proxy.com?url=https://requestedurl.com";
    yield return "https://proxy2.com?url=https://requestedurl.com";
}
 
async Task<HttpResponseMessage> Main()
{
    var addressIterator = GetAddresses().GetEnumerator();
    return await retryPolicy.ExecuteAsync(async () => 
    { 
        addressIterator.MoveNext();
        return await client.GetAsync(addressIterator.Current);
    });   
}
  • The GetAddresses method contains the initial url and the fallback urls as well
  • The retry policy wraps the HttpClient call as well as the url retrieval logic
  • For the sake of brevity I did not check the result of the MoveNext call but you should
    • Whenever you receive false that's when you have run out of fallback urls
    • This case should be handled in appropriate way

Option 2 - Use context and onRetry

IEnumerable<string> GetFallbackAddresses()
{
    yield return "https://proxy.com?url=https://requestedurl.com";
    yield return "https://proxy2.com?url=https://requestedurl.com";
}

const string UrlKey = "url";
IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
  var fallbackAddressIterator = GetFallbackAddresses().GetEnumerator();
  return Policy<HttpResponseMessage>
    ...
    .WaitAndRetryAsync(...,
        onRetry: (_, __, ctx) =>
        {
            fallbackAddressIterator.MoveNext();
            ctx[UrlKey] = fallbackAddressIterator.Current;
        });
}

async Task<HttpResponseMessage> Main()
{
    var retryPolicy = GetRetryPolicy();
    var context = new Context();
    context[UrlKey] = "https://requestedurl.com";
    return await retryPolicy.ExecuteAsync(
           async (ctx) => await client.GetAsync(ctx[UrlKey]), context);
}
  • The GetFallbackAddresses method contains only the fallback urls
  • The retry policy takes care of the update of fallback url
  • For the sake of brevity I did not check the result of the MoveNext call but you should
    • Whenever you receive false that's when you have run out of fallback urls
    • This case should be handled in appropriate way
  • The retry policy wraps only the HttpClient call
    • It receives the url from the context
    • The initial url should be directly provided for the very first attempt
    • The later attempts are handled by the onRetry

Comparison

Concern Option #1 Option #2
Enumerator usage At execution Before next retry attempt
Initial and fallback urls handling In the same way Separately
HttpClient and Enumerator coupling Explicit Implicit via Context

How to apply this to AddHttpClient

As you can see in both options we are directly using the retry policy either because we need to decorate multi commands (Opt. #1) or because we need to pass extra information (Opt. #2).

Binding the policy and the HttpClient via the AddPolicyHandler is not a good option here. We should use a policy registry instead. The policy definition can be added to a registry during application launch:

var registry = new PolicyRegistry()
{
    { "MyPolicy", GetRetryPolicy() }
};
services.AddPolicyRegistry(registry);

and the registry can be injected next to the HttpClient

private readonly HttpClient client;
private readonly IAsyncPolicy<HttpResponseMessage> retryPolicy;
MyClass(HttpClient client, IReadOnlyPolicyRegistry<string> registry)
{
    this.client = client;
    retryPolicy = registry.Get<IAsyncPolicy<HttpResponseMessage>>("MyPolicy");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM