Implementing HTTP Timeout Retries with Polly and IHttpClientBuilder

By FoxLearn 1/9/2025 3:10:38 AM   72
Polly's retry functionality combined with IHttpClientBuilder provides an elegant solution to configure retry logic at the application startup.

By defining the retry behavior in one central location, you ensure that no retry-related code is mixed into the actual HttpClient calls.

The retry mechanism in Polly is implemented using policies, which determine when and how retries are attempted. While retrying on HTTP errors such as 404 Not Found or 500 Internal Server Error is straightforward, dealing with HTTP timeouts is a little different. In this case, HttpClient does not receive a response code, but rather throws a TimeoutRejectedException when the request exceeds the time limit.

To configure retry logic for timeouts, we need to define a retry policy and wrap it with a timeout policy.

Install Required NuGet Packages

Make sure you have the following NuGet packages in your project:

  • Polly
  • Microsoft.Extensions.Http.Polly

Configure IHttpClientBuilder and Polly Policies in Startup

In your Startup.cs, add an HttpClient configuration with the retry and timeout policies.

public static IHostBuilder CreateHostBuilder(string[] args)
{
  var host = Host.CreateDefaultBuilder(args);
  host.ConfigureServices((hostContext, services) =>
  {
    // Register HttpClient with Polly retry and timeout policies
    services.AddHttpClient("HttpClient")
      .AddPolicyHandler(GetRetryPolicy())
      .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(5)); // Timeout after 5 seconds
  });
  return host;
}

private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
  return HttpPolicyExtensions
    .HandleTransientHttpError() // Handles transient HTTP errors like 500 or 502
    .Or<TimeoutRejectedException>() // Also handle timeouts
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(30)); // Retry 3 times with a 30s delay
}

In this example:

  • services.AddHttpClient("HttpClient"): Registers a named HttpClient instance with the retry and timeout policies.
  • Retry Policy: Uses HttpPolicyExtensions.HandleTransientHttpError() to handle common transient HTTP errors (e.g., 500, 502) and adds handling for TimeoutRejectedException to retry on timeouts. The retry happens up to 3 times with a delay of 30 seconds.
  • Timeout Policy: Configures a timeout policy using Policy.TimeoutAsync<HttpResponseMessage>(5), which throws a TimeoutRejectedException if the request takes longer than 5 seconds.

Using IHttpClientFactory

Now, the class making the HTTP requests doesn't need to be aware of the retry logic or timeout settings. The retry and timeout policies are automatically applied by HttpClientFactory.

namespace Demo
{
  public class ExampleHttpClientFactory
  {
    private readonly IHttpClientFactory _clientFactory;

    public ExampleHttpClientFactory(IHttpClientFactory clientFactory)
    {
      _clientFactory = clientFactory;
    }

    public async Task<string> GetAsync(string url)
    {
      var httpClient = _clientFactory.CreateClient("HttpClient");

      // Set Basic Authentication header if required
      SetBasicAuthHeader(httpClient, "user", "password");

      // Execute GET request
      using var response = await httpClient.GetAsync(url);
      
      // Ensure successful response (throws exception on failure)
      response.EnsureSuccessStatusCode();

      // Return response content as string
      return await response.Content.ReadAsStringAsync();
    }

    private void SetBasicAuthHeader(HttpClient httpClient, string username, string password)
    {
      var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
      httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authToken);
    }
  }
}

In this example, httpClient.GetAsync() will automatically retry the request based on the retry policy you defined earlier. The retry will happen if the conditions in the GetRetryPolicy() occur, and it will stop after a successful response or after exhausting the retry attempts.

Avoid Setting HttpClient.Timeout

Be cautious not to set a global HttpClient.Timeout when using Polly’s timeout policy. Setting a timeout will override Polly’s retry logic, and you might get a TaskCanceledException or OperationCanceledException instead of a TimeoutRejectedException, which won't be handled by your timeout policy.

By configuring the retry and timeout policies in IHttpClientBuilder, you keep your HTTP client calls clean and maintainable, allowing retry logic to be applied seamlessly at the service level.