Reputation: 73
I am trying to create a solution with polly where I request an other api.
I have a list of URLs to multiple instances of the same service.
I want that when the first request failes, an other should automaticly start with the next url from my list.
Here is an example where i try this behaviour with two static addresses
The Problem with this solution is that the url does not change until i start the next request.
I want that the urls changes on every retry
public static void ConfigureUserServiceClient(this IServiceCollection services)
{
_userServiceUri = new Uri("https://localhost:5001");
services.AddHttpClient("someService", client =>
{
client.BaseAddress = _userServiceUri;
client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddPolicyHandler(retryPolicy());
}
private static IAsyncPolicy<HttpResponseMessage> retryPolicy()
{
return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt),
onRetry: (result, span, ctx) =>
{
_userServiceUri = new Uri("https://localhost:5002");
});
}
Upvotes: 5
Views: 3160
Reputation: 22819
You should consider to use the Fallback policy instead.
Like this:
private static HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
var addressIterator = GetUrls().GetEnumerator();
var retryLikePolicy = Policy<string>
.Handle<HttpRequestException>()
.FallbackAsync(fallbackAction: async (ct) =>
{
if (addressIterator.MoveNext())
return await GetData(addressIterator.Current);
return null;
});
addressIterator.MoveNext();
var data = await retryLikePolicy.ExecuteAsync(
async () => await GetData(addressIterator.Current));
Console.WriteLine("End");
}
static async Task<string> GetData(string uri)
{
Console.WriteLine(uri);
var response = await client.GetAsync(uri);
return await response.Content.ReadAsStringAsync();
}
static IEnumerable<string> GetUrls()
{
yield return "http://localhost:5500/index.html";
yield return "http://localhost:5600/index.html";
yield return "http://localhost:5700/index.html";
}
Please note that this code is just for demonstration.
UPDATE #1: Multiple fallback
If you have more than one fallback urls then you can alter the above code like this:
private static HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
var retryInCaseOfHRE = Policy
.Handle<HttpRequestException>()
.WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(1));
var response = await retryInCaseOfHRE.ExecuteAsync(
async () => await GetNewAddressAndPerformRequest());
if (response == null)
{
Console.WriteLine("All requests failed");
Environment.Exit(1);
}
Console.WriteLine("End");
}
static IEnumerable<string> GetAddresses()
{
yield return "http://localhost:5500/index.html";
yield return "http://localhost:5600/index.html";
yield return "http://localhost:5700/index.html";
yield return "http://localhost:5800/index.html";
}
static readonly IEnumerator<string> AddressIterator = GetAddresses().GetEnumerator();
static async Task<string> GetNewAddressAndPerformRequest()
=> AddressIterator.MoveNext() ? await GetData(AddressIterator.Current) : null;
static async Task<string> GetData(string uri)
{
Console.WriteLine(uri);
var response = await client.GetAsync(uri);
return await response.Content.ReadAsStringAsync();
}
GetData
GetNewAddressAndPerformRequest
)Fallback
policy to Retry
since we need to perform (potentially) more than 1 fallback actionsnull
to indicate we have run out of fallback urls but it might be a better solution to use a custom exception for thatUpvotes: 6