Modify request before retrying
Currently the FluentHttpClient allows us to handle scenarios where a request fails (presumably due to a transient problem) and waiting a brief moment allows us to dispatch the exact same request. An example is when you exceed the number of requests that an API allows you to make in a given period of time. In this scenario, you can typically reissue the exact same request after waiting a second or two. This is working quite well.
However, there are more advanced scenarios that I feel FluentHttpClient is not allowing us to handle. Let me offer an example to illustrate what I mean: let's say we issue a request with a "bearer" authentication token and the response is HTTP 401 due to the token being expired. I can write a custom IRetryConfig that will automatically refresh the token, the ShouldRetry in my IRetryConfig class will return true to indicate that I want the request to be retried but I have no way of replacing the expired token with the new token. Again, this is just an example. What I'm trying to illustrate is a scenario where I need the request to be slightly modified before being retried.
To kick-start the discussion about how to handle this kind of scenarios, I would like to propose adding an additional parameter to the ShouldRetry and/or GetDelay methods in the IRetry interface like so:
// Current signature:
bool ShouldRetry(HttpResponseMessage response);
TimeSpan GetDelay(int retry, HttpResponseMessage response);
// What I propose:
bool ShouldRetry(HttpResponseMessage response, IRequest request);
TimeSpan GetDelay(int retry, HttpResponseMessage response, IRequest request);
This would allow developers to modify the request in their implementation of the ShouldRetry and/or GetDelay methods prior to the request being reissued.
If you think my suggestion makes sense, I'll be happy to submit a PR.
Hi! Request coordinators are meant for scenarios like that. You can only have one request coordinator currently, but you can wrap the default one with custom logic. For example:
/// <summary>A coordinator that automatically updates the token if it expired, else applies retry policies.</summary>
public class AutoRefreshRetryCoordinator : IRequestCoordinator
{
/// <summary>The default retry logic.</summary>
private readonly IRequestCoordinator RetryCoordinator;
/// <summary>Construct an instance.</summary>
/// <param name="configs">The retry configurations to apply.</param>
public AutoRefreshRetryCoordinator(IEnumerable<IRetryConfig?>? configs)
{
this.RetryCoordinator = new RetryCoordinator(configs);
}
/// <inheritdoc />
public async Task<HttpResponseMessage> ExecuteAsync(IRequest request, Func<IRequest, Task<HttpResponseMessage>> dispatcher)
{
try
{
return await this.RetryCoordinator.ExecuteAsync(request, dispatcher);
}
catch (ApiException ex) when (ex.Status == HttpStatusCode.Unauthorized)
{
string newToken = ...;
return await this.RetryCoordinator.ExecuteAsync(request.WithBearerAuthentication(newToken), dispatcher);
}
}
}
I have a custom request coordinator for this purpose but I was thinking it would be simpler to allow RetryConfigs to modify the request.