dotnet / extensions

This repository contains a suite of libraries that provide facilities commonly needed when creating production-ready applications.
MIT License
2.58k stars 740 forks source link

[API Proposal]: Allow retrieval of HttpRequestMessage from `Polly.Context` #4957

Open martintmk opened 6 months ago

martintmk commented 6 months ago

Background and motivation

Clients using new resilience APIs based on top of Polly v8 might want to access HttpRequestMessage associated with the attempt being executed. This would allow the following scenarios:

Metering Enrichment

Client can decide to add additional metrics extracted from HttpRequestMessage using metering enrichment.

Custom Routing

For hedging or retries, the client might decide to change the URL for secondary attempts. This would also enable dynamic URL resolution.

Access to HttpRequestMessage in callbacks

For logging/other purposes the client uses callbacks and can be interested in the current request message.

API Proposal

namespace Polly;

public static class HttpResilienceContextExtensions
{
    public static HttpRequestMessage? GetRequestMessage(this ResilienceContext context);
    public static void SetRequestMessage(this ResilienceContext context, HttpRequestMessage? requestMessage);
}

API Usage

services
    .AddHttpClient("my-client", client => client.BaseAddress = new Uri("https://primary-endpoint"))
    .AddStandardHedgingHandler()
    .Configure(options =>
    {
        options.Hedging.OnHedging = args =>
        {
            // Retrieve the request message
            HttpRequestMessage request = args.ActionContext.GetRequestMessage();

            UriBuilder uriBuilder = new(request.RequestUri);
            uriBuilder.Host = "secondary-endpoint";

            // Override the request URI
            request.RequestUri = uriBuilder.Uri;

            return default;
        };
    });

The example above demonstrates how the new API can be used to change the URL of outgoing request. Similar approach can be used for retries or telemetry enrichment.

Alternative Designs

The above example can be rewritten as:

ResiliencePropertyKey<HttpRequestMessage> requestMessageKey = new("Resilience.Http.RequestMessage");

services
    .AddHttpClient("my-client", client => client.BaseAddress = new Uri("https://primary-endpoint"))
    .AddStandardHedgingHandler()
    .Configure(options =>
    {
        options.Hedging.OnHedging = args =>
        {
            // Retrieve the request message
            HttpRequestMessage? request = args.ActionContext.Properties.GetValue(requestMessageKey, null!);

            UriBuilder uriBuilder = new(request.RequestUri);
            uriBuilder.Host = "secondary-endpoint";

            // Override the request URI
            request.RequestUri = uriBuilder.Uri;

            return default;
        };
    });

However, this relies on some internal naming: https://github.com/dotnet/extensions/blob/a8e17517bd5150cc0f992b66aa6591c7c6fbdfc4/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ResilienceKeys.cs#L13C92-L13C122

It would be better to have built-in support for retrieving HttpRequestMessage from ResilienceContext as I suspect this will be a common scenario.

Risks

Caller retrieving and modifying HttpRequestMessage associated with the current attempt in a non-compatible way or doing some changes that will corrupt the request.

joperezr commented 6 months ago

Given you are the main expert on this, are you expecting any feedback from anyone else @martintmk or is this ready for review?

martintmk commented 6 months ago

Given you are the main expert on this, are you expecting any feedback from anyone else @martintmk or is this ready for review?

I believe this is ready for review.

davemyers-dev commented 3 weeks ago

@martintmk I'd love to see this built in! We're trying to rollout new v8 policy guidance & extensions for our enterprise and using information from the request message in our Retry logging is a common use case.

iliar-turdushev commented 2 weeks ago

@martintmk I have 2 questions:

Apart from that, the proposal looks good to me.

martintmk commented 2 weeks ago

Hey @iliar-turdushev

As I understand, SetRequestMessage will be useful for the Hedging handler when a user wants to replace the request object with a custom one, right?

Yup, they can also access the request message, extract some information and use it for telemetry. The latter was my primary motivation for these APIs.

Do we want to make the second argument of the SetRequestMessage method non-nullable, i.e. SetRequestMessage(this ResilienceContext context, HttpRequestMessage requestMessage)?

I think we shall still keep the option to "reset" the request message. So my vote would be to keep the nullable option.

RussKie commented 2 weeks ago

@joperezr @geeknoid any thoughts or objections?

geeknoid commented 2 weeks ago

LGTM.

RussKie commented 2 weeks ago

Approved.

@martintmk feel free to send a pull-request with the necessary changes, if you fancy it.