asp. net core adds dynamic naming configuration to IHttpClientFactory
- 2021-11-29 23:34:19
- OfStack
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)
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 overwriterequiredService
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!