asp. net core adds dynamic naming configuration to IHttpClientFactory

  • 2021-11-29 23:34:19
  • OfStack

Do you have any official recommendations from the catalogue? How did IHttpClientFactory. CreateClient create HttpClient? Implementation of Extension Point 1 Implementation of Extension Point 2 uses
Summary 1

For example, how to dynamically add cer certificates using IHttpClientFactory

There are 3 recommended methods

Method 1: The recommended practice is this

services.AddHttpClient("a Business ").ConfigurePrimaryHttpMessageHandler(...a Business certificate )
services.AddHttpClient("b Business ").ConfigurePrimaryHttpMessageHandler(...b Business certificate )
ServiceProvider.GetService<IHttpClientFactory>().CreateClient("a Business ")....
Method 2:

If you want to completely customize, you can use new System.Net.Http.HttpClient(handler)

Method 3:

In or with Sao operation, the way of replacing configuration can also be logically to implement an HttpClientFactoryOptions of its own, and then generate it dynamically.
get_cert_handler_by_name is your own method, which can distinguish the service names a, b, c new by any one handler.
However, note that all HttpClient obtained from ServiceProvider will go to this custom configuration class, and compatibility should be done well.


class MyClass : IPostConfigureOptions<HttpClientFactoryOptions>
        {
            public void PostConfigure(string name, HttpClientFactoryOptions options)
                => options.HttpMessageHandlerBuilderActions.Add(p => p.PrimaryHandler = get_cert_handler_by_name(name));
        }
// Register for this service 
services.AddSingleton<Microsoft.Extensions.Options.IPostConfigureOptions<Microsoft.Extensions.Http.HttpClientFactoryOptions>, MyClass>();

The above is a summary of the previous situation, so let's implement this requirement next.

Seconds to think of a way, we can directly new HttpClient() , come directly to one every time you want to use it, which is simple and rude.
Second to think of the second method, or use 1 Dictionary<string,HttpClient> Cache client objects by name.
However, the performance of the former is a problem, and it involves the occupation and release of ports, which is cool when the call volume is slightly large, while the latter has a known problem that HttpClient objects cannot sense the changes of dns.

Other less reliable methods are: using code configuration ( services.AddHttpClient("callback provider side").ConfigurePrimaryHttpMessageHandler() ) Configure all certificates, as well as the local machine where all certificates are installed and set to trust certificates.

So can there be reliable ways besides the above unreliable ways (or fatally flawed ways)? Of course, there are some, such as the implementation scheme of dynamic configuration at runtime.

So, next, let me recommend two ways, which are ours IHttpMessageHandlerBuilderFilter And IPostConfigureOptions .

Do you have any official recommendations?

For how to add a certificate to an HttpClient object, The implementation of the official document is: Implement HttpClient with certificate and named HttpClient from IHttpClientFactory and HttpClient with certificate and HttpClientHandler, But it obviously doesn't address our runtime configuration needs here, but it gives a clue, which is named configuration. It can provide custom configuration for every one of our different provider. As long as we can provide runtime configuration for every one of our different provider, then there is the source code reading time:

All of the code below comes from netcore 3.1, and only copy key code. The complete code can be viewed at github.

How did IHttpClientFactory. CreateClient create HttpClient?

Every time CreateClient All that came out was a new one HttpClient Instances In CreateHandler In _activeHandlers Our handler will be cached for us, and the default is 2 minutes (defined in new HttpClient()0 ) One thing to know is that if my request gets this cached object just 1 o'clock before the expiration time, It is possible that my current request is still in progress, but this handler will be recycled after 2 minutes. How does the official solve this possible bug for us? Please see the article Cleaning up expired handlers, I will not repeat it, the key point is to use 1 WeakReference CreateHandlerEntry Method is where we really create and configure our handlers. From IConfiguration Get 1 HttpClientFactoryOptions Object Application IHttpMessageHandlerBuilderFilter Application HttpMessageHandlerBuilderActions

//Microsoft.Extensions.Http.DefaultHttpClientFactory
public HttpClient CreateClient(string name)
{
	HttpClient httpClient = new HttpClient(this.CreateHandler(name), disposeHandler: false);
	return httpClient;
}
public HttpMessageHandler CreateHandler(string name)
{
	ActiveHandlerTrackingEntry value = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value; 
        //_entryFactory It can be directly understood as yes CreateHandlerEntry Method . Its true type is Lazy<>(CreateHandlerEntry,LazyThreadSafetyMode.ExecutionAndPublication) Adj. ,  That is, concurrent safe calls CreateHandlerEntry.
	return value.Handler;
}
internal ActiveHandlerTrackingEntry CreateHandlerEntry(string name)
{
        HttpClientFactoryOptions options = this._optionsMonitor.Get(name);
	HttpMessageHandlerBuilder requiredService = provider.GetRequiredService<HttpMessageHandlerBuilder>();
	requiredService.Name = name;
	Action<HttpMessageHandlerBuilder> action = Configure; //  Extension point 2 HttpClientFactoryOptions.HttpMessageHandlerBuilderActions
	for (int num = this._filters.Length - 1; num >= 0; num--)
	{
		action = this._filters[num].Configure(action); // Extension point 1 _filters( Constructor passed in IEnumerable<IHttpMessageHandlerBuilderFilter> filters).
	}
	action(requiredService);
	LifetimeTrackingHttpMessageHandler handler = new LifetimeTrackingHttpMessageHandler(requiredService.Build());
	return new ActiveHandlerTrackingEntry(name, handler, serviceScope, options.HandlerLifetime);

	void Configure(HttpMessageHandlerBuilder b)
	{
		for (int i = 0; i < options.HttpMessageHandlerBuilderActions.Count; i++)
		{
			options.HttpMessageHandlerBuilderActions[i](b);
		}
	}
}

The key point code is the extension point 1 and extension point 2 marked in the above code.

Extension point 1: You need to inject the appropriate IHttpMessageHandlerBuilderFilter object to overwrite requiredService Object, we can realize the runtime dynamic configuration we want. Extension point 2: You need to implement a custom IConfiguration configuration, as long as this._optionsMonitor.Get(name) Of the object you got HttpMessageHandlerBuilderActions Property contains our corresponding overwrite code.

Implementation of Extension Point 1

Add a configured filter to handler of HttpClient, and add some rewriting logic of its own for handlerBuilder conforming to handlerBuilder.
The logs ("Sending HTTP request......", "Received HTTP response headers after......") generated when we use the HttpClient object are injected by this Filter feature. Official reference code: LoggingHttpMessageHandlerBuilderFilter

Personal opinion: I feel that adding this business at this extension point is not particularly suitable for the application scenario, so I suggest doing this at Extension Point 2.


class MyHttpClientHandlerFilter : IHttpMessageHandlerBuilderFilter
{
    public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
    {
        void Configure(HttpMessageHandlerBuilder builder)
        {
            next(builder); //1 Call at the beginning next,  So that our whole HandlerBuilder The execution order of is in turn call _filters,  Finally call options.HttpMessageHandlerBuilderActions( Extension point 2).

            if (builder.Name.StartsWith("CallbackProviderSide-")) // We can arrange for this kind of business 1 Plus 1 Prefix to distinguish ,  So it won't affect other HttpClient Object .
            {
                //builder.PrimaryHandler= your custom handler.  Reference to the implementation of official documents .
            }
        }
        return Configure;
    }
}
// And then in DI The container is filled with our filter.
ServiceCollection.AddSingleton<IHttpMessageHandlerBuilderFilter,MyHttpClientHandlerFilter>();

Implementation of Extension Point 2


class MyHttpClientCustomConfigure : IPostConfigureOptions<HttpClientFactoryOptions>
{
    public void PostConfigure(string name, HttpClientFactoryOptions options)
    {
        if (name.StartsWith("CallbackProviderSide-")) // We can arrange for this kind of business 1 Plus 1 Prefix to distinguish ,  So it won't affect other HttpClient Object .
        {
            options.HttpMessageHandlerBuilderActions.Add(p =>
            {
                //p.PrimaryHandler= your custom handler.  Reference to the implementation of official documents .
            });
        }
    }
}

// And then in DI Inject our configuration extension class into the container .
ServiceCollection.AddSingleton<Microsoft.Extensions.Options.IPostConfigureOptions<Microsoft.Extensions.Http.HttpClientFactoryOptions>, MyHttpClientCustomConfigure>();

Why is the type injected here Dictionary<string,HttpClient>0 , because Dictionary<string,HttpClient>1 This is what its constructor needs. As for the extension and source code of Configuration system, it will not be expanded here.

Use

As for using it, it is simple


var factory = ServiceProvider.GetService<IHttpClientFactory>();
var httpClientForBaidu = factory.CreateClient("CallbackProviderSide-baidu");
var httpClientForCnblogs = factory.CreateClient("CallbackProviderSide-Cnblogs");

Summary 1

In this way, even if our runtime dynamic configuration of HttpClient is completed, I can easily write another article.
In addition, related IHttpClientFactory The story behind you can see the article Exploring the code behind IHttpClientFactory in depth. The complete flow chart is matched with the code to explain it clearly.

The above is the details of asp. net core adding dynamic naming configuration to IHttpClientFactory. For more information about asp. net core adding dynamic naming configuration, please pay attention to other related articles on this site!


Related articles: