App-vNext / Polly

Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. From version 6.0.1, Polly targets .NET Standard 1.1 and 2.0+.
https://www.thepollyproject.org
BSD 3-Clause "New" or "Revised" License
13.48k stars 1.23k forks source link

[Question]: How to define a Policy per HttpMessage Method Type (GET, POST etc.) #2403

Open sndpyadav34 opened 6 days ago

sndpyadav34 commented 6 days ago

What are you wanting to achieve?

I have a common HttpClient that is used or sending multiple requests like GET, POST, PUT etc. I am trying to configure different retry behavior for GET versus POST/PUT calls. Basically I am trying to configure a timeout per retry behavior for GET calls and a transient error behavior for POST/PUT calls.

What code or approach do you have so far?

I defined following two policies. policy1 handles the result of GET requests and handles the TimeoutRejectedException along with transient errors whereas policy2 handles transient errors for requests other than GET. I then wrap both policies.

Random jitterer = new Random();

var policy1 = Polly.Policy
        .HandleResult<HttpResponseMessage>(req => req.RequestMessage.Method == HttpMethod.Get)
        .OrTransientHttpError()
        .Or<BrokenCircuitException>()
        .Or<TimeoutRejectedException>()
        .WaitAndRetryAsync(3,
        sleepDurationProvider: (retryAttempt, ctx) =>
        {
            return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
                        + TimeSpan.FromMilliseconds(jitterer.Next(1000, 3000));
        },
        onRetry: (outcome, timespan, retryAttempt, context) =>
        {
            //LogRetry(services, outcome, timespan, retryAttempt, context);
        });

var policy2 = Polly.Policy
        .HandleResult<HttpResponseMessage>(req => req.RequestMessage.Method != HttpMethod.Get)
        .OrTransientHttpError()
        .Or<BrokenCircuitException>()
        .WaitAndRetryAsync(3,
        sleepDurationProvider: (retryAttempt, ctx) =>
        {
            return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
                        + TimeSpan.FromMilliseconds(jitterer.Next(1000, 3000));
        },
        onRetry: (outcome, timespan, retryAttempt, context) =>
        {
            //LogRetry(services, outcome, timespan, retryAttempt, context);
        });

var combinedPolicy = Polly.Policy.WrapAsync(policy1, policy2);

And then while registering the HttpClient I add the retry policy handlers as below:

services.AddHttpClient<IPlatformHttpClient, PlatformHttpClient>()
    .AddPolicyHandler(combinedPolicy)
    .AddPolicyHandler(GetCircuitBreakerPolicy())
    .AddPolicyHandler(Polly.Policy.TimeoutAsync<HttpResponseMessage>(5));

However, with the policies configured like this, I see retries happening for both GET and POST requests. I think it's because of the timeout policy around the HttpClient. Not sure if it's possible to define on a single client like this.

Additional context

No response

martincostello commented 6 days ago

Here's some code that uses different policies depending on the HTTP method.

Two different policies are added to a policy registry, one for "reads" and one for "writes": https://github.com/martincostello/alexa-london-travel-site/blob/5dc4c38445a6d7e1bb445898f5523b8b5f94eb0f/src/LondonTravel.Site/Extensions/PollyServiceCollectionExtensions.cs

The appropriate policy is then obtained from the registry based on the HTTP method of the current request: https://github.com/martincostello/alexa-london-travel-site/blob/0a6829fc01976f673cf9f26bcbf0dae36d72199a/src/LondonTravel.Site/Extensions/IHttpClientBuilderExtensions.cs#L90

peter-csala commented 6 days ago

Side note

The AddPolicyHandler call registers a PolicyHttpMessageHandler which is a DelegatingHandler. I would suggest to combine all your policies into a single Policy.WrapAsync and register only a single DelegatingHandler instead of three.

.AddPolicyHandler(Policy.WrapAsync(retryForGet, retryForNonGet, circuitBreaker, perRequestTimeout));