Set a default Polly policy with Flurl
Asked Answered
T

2

2

I'm currently using Polly and Flurl together, but I have a common retry policy that I have to add to every request. I notice that Polly allows you to set a default using AddPolicyHandler(...) but this requires an IHttpClientBuilder and I can't see any way of getting hold of this from Flurl.

I thought overloading DefaultHttpClientFactory might be the way to go, but that only gives me access to the HttpClient, not the IHttpClientBuilder.

I know I could make my own HttpClients and pass them into Flurl, but I'd rather avoid that if I can as I'd like Flurl to manage their lifecycle.

Is there currently a way of doing what I want to do?

Trommel answered 11/9, 2018 at 9:2 Comment(0)
N
9

Great question. Flurl gives you all the necessary hooks to do this. First define a DelegatingHandler that takes a Polly policy:

public class PollyHandler : DelegatingHandler
{
    private readonly IAsyncPolicy<HttpResponseMessage> _policy;

    public PollyHandler(IAsyncPolicy<HttpResponseMessage> policy) {
        _policy = policy;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        return _policy.ExecuteAsync(ct => base.SendAsync(request, ct), cancellationToken);
    }
}

Then create a custom IHttpClientFactory that returns your custom handler with the default handler as its InnerHandler:

public class PollyFactory : DefaultHttpClientFactory
{
    private readonly IAsyncPolicy<HttpResponseMessage> _policy;

    public PollyFactory(IAsyncPolicy<HttpResponseMessage> policy) {
        _policy = policy;
    }

    public override HttpMessageHandler CreateMessageHandler() {
        return new PollyHandler(_policy) {
            InnerHandler = base.CreateMessageHandler()
        };
    }
}

Finally, on app startup, define your policy and register it with Flurl:

var policy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .RetryAsync(5);

FlurlHttp.Configure(settings => settings.HttpClientFactory = new PollyFactory(policy));

One important note is that this approach will not work with a policy that handles FlurlHttpException. That's because you're intercepting calls at the HttpMessageHandler level here. Flurl converts responses and errors to FlurlHttpExceptions higher up the stack, so those won't get trapped/retried with this approach. The policy in the example above traps HttpRequestException and HttpResponseMessage (with non-2XX status codes), which will work.

Neodarwinism answered 11/9, 2018 at 20:48 Comment(6)
When is FlurlHttpException thrown vs HttpRequestException?Elongation
@Elongation HttpRequestException is thrown inside any message handler you have configured, like above. FlurlHttpException is thrown by Flurl upon completion of any HTTP call, after those handlers have run. If you're doing try/catch within the normal flow of your app logic, FlurlHttpException is what you want to catch.Neodarwinism
How do I use it without the factory? This only calls it once string s = await Policy .Handle<HttpRequestException> () .OrResult <HttpResponseMessage> (r => !r.IsSuccessStatusCode) .RetryAsync (5) .ExecuteAsync (() => { Console.WriteLine ("Retry"); return "http://127.0.0:7071/".GetAsync (); }) .ReceiveString () .ConfigureAwait (false);Elongation
@Elongation Please ask a new question.Neodarwinism
Good idea, the formatting doesn't work in comments , took over many edits and unable to format properly #55718910Elongation
@ToddMenier According to Stephen Cleary it is not a good idea to use Polly inside the HttpClient: #56769741Eskilstuna
S
0

To achieve the same functionality on Flurl.http v4, you would register the delegation handler as middleware, which is nicely simplified from the old way of achieving this.

As before you start with a DelegationHandler that takes a polly policy:

public class PollyHandler : DelegatingHandler
{
    private readonly IAsyncPolicy<HttpResponseMessage> _policy;

    public PollyHandler(IAsyncPolicy<HttpResponseMessage> policy) {
        _policy = policy;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        return _policy.ExecuteAsync(ct => base.SendAsync(request, ct), cancellationToken);
    }
}

And then you define your policy and register this as middleware on startup:

var policy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .RetryAsync(5);

FlurlHttp.Clients.WithDefaults(clientBuilder => clientBuilder.AddMiddleware(() => new PollyHandler(policy)));
Soothsay answered 9/12, 2023 at 13:57 Comment(3)
Why do you need to define a custom PollyHandler? Can't you just use the PolicyHttpMessageHandler?Swop
FYI...This will only work with Polly v7. I gave a workaround for Polly v8 in this post: #40746309Preindicate
I have just verified on my side this does work on flurl v4 and polly v8.2.0. registering the policy as described above you can then run it using the string extention methods for instance and it will execute as expected. csharp var result = await "http://localhost:3000/api/something".GetJsonAsync<dynamic>(); however I am not sure if this would be considered industry best practice, but it is as far as I have experimented the simplest way to upgrade your old project if done on the way described above.Soothsay

© 2022 - 2024 — McMap. All rights reserved.