Reputation: 713
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?
Upvotes: 2
Views: 3569
Reputation: 22819
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.
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);
});
}
GetAddresses
method contains the initial url and the fallback urls as wellHttpClient
call as well as the url retrieval logicMoveNext
call but you should
false
that's when you have run out of fallback urlsIEnumerable<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);
}
GetFallbackAddresses
method contains only the fallback urlsMoveNext
call but you should
false
that's when you have run out of fallback urlsHttpClient
call
onRetry
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 |
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");
}
Upvotes: 3