How can i invoke X-HttpMessageHandlers when retrying requests?

100 views Asked by At

In short: I want to execute X-HttpMessageHandlers when retrying a request.

Implementation: Currently, I have added an HttpClient, whose request gets handled by a Logging- and a PolicyHandler:

using Microsoft.Extensions.Http.Polly
builder.Services.AddHttpClient()
    .AddHttpMessageHandler<LoggingHandler>()
    .AddPolicyHandler(GetRetryPolicy());

Policy: To keep the policy very simple, let's use an example:

public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    // HandleTransientHttpError: Handles HttpRequestException, 
    // Http status codes >= 500 (server errors) 
    // and status code 408 (request timeout)

    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .RetryAsync(3); // Retry the request up to 3 times
}

In case you want to test out the retry policy, send requests to: https://httpbin.org/status/503.

The above link will return an unsuccessful HttpResponseMessage with a status code 502, aka Bad Gateway. This response type is a server error, so it will get handled by the policy.

Sources:

2

There are 2 answers

2
Peter Csala On BEST ANSWER

TL; DR: You just have to change the message handlers registration order

from

.AddHttpMessageHandler<LogHandler>()
.AddPolicyHandler(retryPolicy);

to

.AddPolicyHandler(retryPolicy)
.AddHttpMessageHandler<LogHandler>();

UPDATE #1

In order to better understand why DelegatingHandlers' registration order matter I will extend my original post.

The AddPolicyHandler

This extension method basically registers a PolicyHttpMessageHandler instance into the handlers pipeline. This class implements the DelegatingHandler abstract class in a way that it applies a policy to the outgoing request. That's why the policy's type should be IAsyncPolicy<HttpResponseMessage>.

The important stuff here is that from the HttpClient perspective your LogHandler and this PolicyHttpMessageHandler are treated in the same way.

AddHttpMessageHandler, AddPolicyHandler

If you register the handlers into the HttpClient's pipeline like you did then the output will be:

Loghandler
Retry attempt 1
Retry attempt 2

Lets see what happens under the hood

wrong order

I've used mermaid.js to draw this diagram and I've used autonumbering to be able to correlate action and emitted output.

2: Loghandler
7: Retry attempt 1
10: Retry attempt 2

AddPolicyHandler,AddHttpMessageHandler

This time lets switch the registration order. The output will be:

Loghandler
Retry attempt 1
Loghandler
Retry attempt 2
Loghandler

The actions sequence:

good order

and finally the correlated log:

4: Loghandler
8: Retry attempt 1
10: Loghandler
14: Retry attempt 2
16: Loghandler

For more details please check out this SO topic.

Here you can find a dotnet fiddle to be able to play with it.

1
Fengzhi Zhou On

Integrate your logging in the policy. Here is a simple sample.

Your program

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(response => !response.IsSuccessStatusCode)
    .WaitAndRetryAsync(2, retryCount => TimeSpan.FromSeconds(2),
        onRetry: (outcome, timespan, retryCount, context) =>
        {
            var logHandler = new LogHandler();
            logHandler.LogRetryAttempt(outcome, retryCount);
        });

Your loghandler

public class LogHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("LogHandler");
        var response = await base.SendAsync(request, cancellationToken);
        return response;
    }

    // Add this method to log retry attempts
    public void LogRetryAttempt(DelegateResult<HttpResponseMessage> outcome, int retryCount)
    {
        // Log the details of the retry attempt
        if (outcome.Exception != null)
        {
            Debug.WriteLine($"Retry {retryCount} due to an exception: {outcome.Exception.Message}");
        }
        else if (outcome.Result != null)
        {
            Debug.WriteLine($"Retry {retryCount} due to a non-success status code: {outcome.Result.StatusCode}");
        }
    }
}

enter image description here