一、前言
前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandler
和PrimaryHttpMessageHandler
的使用场景和区别
二、源代码阅读
2.1 核心消息管道模型图
先贴上一张核心MessageHandler 管道模型的流程图,图如下:HttpClient 中的HttpMessageHandler
负责主要核心的业务,HttpMessageHandler
是由MessageHandler 链表结构组成,形成一个消息管道模式;具体我们一起来看看源代码
2.2 Demo代码演示
再阅读源代码的时候我们先来看下下面注入HttpClient
的Demo 代码,代码如下:
services.AddHttpClient("test").ConfigurePrimaryHttpMessageHandler(provider =>{return new PrimaryHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new LogHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new Log2HttpMessageHandler(provider);});
上面代码中有两个核心扩展方法,分别是ConfigurePrimaryHttpMessageHandler
和AddHttpMessageHandler
,这两个方法大家可能会有疑问是做什么的呢?不错,这两个方法就是扩展注册自定义的HttpMessageHandler
如果不注册,会有默认的HttpMessageHandler
,接下来我们分别来看下提供的扩展方法,如下图:图中提供了一系列的AddHttpMessageHandler
扩展方法和ConfigurePrimaryHttpMessageHandler
的扩展方法。
2.3 AddHttpMessageHandler
我们来看看HttpClientBuilderExtensions
中的其中一个AddHttpMessageHandler
扩展方法,代码如下:
/// <summary>/// Adds a delegate that will be used to create an additional message handler for a named <see cref="HttpClient"/>./// </summary>/// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>/// <param name="configureHandler">A delegate that is used to create a <see cref="DelegatingHandler"/>.</param>/// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>/// <remarks>/// The <see paramref="configureHandler"/> delegate should return a new instance of the message handler each time it/// is invoked./// </remarks>public static IHttpClientBuilder AddHttpMessageHandler(this IHttpClientBuilder builder, Func<DelegatingHandler> configureHandler){if (builder == null){throw new ArgumentNullException(nameof(builder));}if (configureHandler == null){throw new ArgumentNullException(nameof(configureHandler));}builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>{options.HttpMessageHandlerBuilderActions.Add(b => b.AdditionalHandlers.Add(configureHandler()));});return builder;}
代码中把自定义的DelegatingHandler
方法添加到HttpMessageHandlerBuilderActions
中,我们再来看看HttpClientFactoryOptions
对象源代码,如下:
/// <summary>/// An options class for configuring the default <see cref="IHttpClientFactory"/>./// </summary>public class HttpClientFactoryOptions{// Establishing a minimum lifetime helps us avoid some possible destructive cases.//// IMPORTANT: This is used in a resource string. Update the resource if this changes.internal readonly static TimeSpan MinimumHandlerLifetime = TimeSpan.FromSeconds(1);private TimeSpan _handlerLifetime = TimeSpan.FromMinutes(2);/// <summary>/// Gets a list of operations used to configure an <see cref="HttpMessageHandlerBuilder"/>./// </summary>public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();/// <summary>/// Gets a list of operations used to configure an <see cref="HttpClient"/>./// </summary>public IList<Action<HttpClient>> HttpClientActions { get; } = new List<Action<HttpClient>>();/// <summary>/// Gets or sets the length of time that a <see cref="HttpMessageHandler"/> instance can be reused. Each named/// client can have its own configured handler lifetime value. The default value of this property is two minutes./// Set the lifetime to <see cref="Timeout.InfiniteTimeSpan"/> to disable handler expiry./// </summary>/// <remarks>/// <para>/// The default implementation of <see cref="IHttpClientFactory"/> will pool the <see cref="HttpMessageHandler"/>/// instances created by the factory to reduce resource consumption. This setting configures the amount of time/// a handler can be pooled before it is scheduled for removal from the pool and disposal./// </para>/// <para>/// Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating/// more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely/// which can prevent the handler from reacting to DNS changes. The value of <see cref="HandlerLifetime"/> should be/// chosen with an understanding of the application's requirement to respond to changes in the network environment./// </para>/// <para>/// Expiry of a handler will not immediately dispose the handler. An expired handler is placed in a separate pool/// which is processed at intervals to dispose handlers only when they become unreachable. Using long-lived/// <see cref="HttpClient"/> instances will prevent the underlying <see cref="HttpMessageHandler"/> from being/// disposed until all references are garbage-collected./// </para>/// </remarks>public TimeSpan HandlerLifetime{get => _handlerLifetime;set{if (value != Timeout.InfiniteTimeSpan && value < MinimumHandlerLifetime){throw new ArgumentException(Resources.HandlerLifetime_InvalidValue, nameof(value));}_handlerLifetime = value;}}/// <summary>/// The <see cref="Func{T, R}"/> which determines whether to redact the HTTP header value before logging./// </summary>public Func<string, bool> ShouldRedactHeaderValue { get; set; } = (header) => false;/// <summary>/// <para>/// Gets or sets a value that determines whether the <see cref="IHttpClientFactory"/> will/// create a dependency injection scope when building an <see cref="HttpMessageHandler"/>./// If <c>false</c> (default), a scope will be created, otherwise a scope will not be created./// </para>/// <para>/// This option is provided for compatibility with existing applications. It is recommended/// to use the default setting for new applications./// </para>/// </summary>/// <remarks>/// <para>/// The <see cref="IHttpClientFactory"/> will (by default) create a dependency injection scope/// each time it creates an <see cref="HttpMessageHandler"/>. The created scope has the same/// lifetime as the message handler, and will be disposed when the message handler is disposed./// </para>/// <para>/// When operations that are part of <see cref="HttpMessageHandlerBuilderActions"/> are executed/// they will be provided with the scoped <see cref="IServiceProvider"/> via/// <see cref="HttpMessageHandlerBuilder.Services"/>. This includes retrieving a message handler/// from dependency injection, such as one registered using/// <see cref="HttpClientBuilderExtensions.AddHttpMessageHandler{THandler}(IHttpClientBuilder)"/>./// </para>/// </remarks>public bool SuppressHandlerScope { get; set; }}
源代码中有如下核心List:
public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();
提供了HttpMessageHandlerBuilder
HttpMessageHandler 的构造器列表对象,故,通过AddHttpMessageHandler
可以添加一系列的消息构造器方法对象 我们再来看看这个消息构造器类,核心部分,代码如下:
public abstract class HttpMessageHandlerBuilder{/// <summary>/// Gets or sets the name of the <see cref="HttpClient"/> being created./// </summary>/// <remarks>/// The <see cref="Name"/> is set by the <see cref="IHttpClientFactory"/> infrastructure/// and is public for unit testing purposes only. Setting the <see cref="Name"/> outside of/// testing scenarios may have unpredictable results./// </remarks>public abstract string Name { get; set; }/// <summary>/// Gets or sets the primary <see cref="HttpMessageHandler"/>./// </summary>public abstract HttpMessageHandler PrimaryHandler { get; set; }/// <summary>/// Gets a list of additional <see cref="DelegatingHandler"/> instances used to configure an/// <see cref="HttpClient"/> pipeline./// </summary>public abstract IList<DelegatingHandler> AdditionalHandlers { get; }/// <summary>/// Gets an <see cref="IServiceProvider"/> which can be used to resolve services/// from the dependency injection container./// </summary>/// <remarks>/// This property is sensitive to the value of/// <see cref="HttpClientFactoryOptions.SuppressHandlerScope"/>. If <c>true</c> this/// property will be a reference to the application's root service provider. If <c>false</c>/// (default) this will be a reference to a scoped service provider that has the same/// lifetime as the handler being created./// </remarks>public virtual IServiceProvider Services { get; }/// <summary>/// Creates an <see cref="HttpMessageHandler"/>./// </summary>/// <returns>/// An <see cref="HttpMessageHandler"/> built from the <see cref="PrimaryHandler"/> and/// <see cref="AdditionalHandlers"/>./// </returns>public abstract HttpMessageHandler Build();protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable<DelegatingHandler> additionalHandlers){// This is similar to https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Net.Http.Formatting/HttpClientFactory.cs#L58// but we don't want to take that package as a dependency.if (primaryHandler == null){throw new ArgumentNullException(nameof(primaryHandler));}if (additionalHandlers == null){throw new ArgumentNullException(nameof(additionalHandlers));}var additionalHandlersList = additionalHandlers as IReadOnlyList<DelegatingHandler> ?? additionalHandlers.ToArray();var next = primaryHandler;for (var i = additionalHandlersList.Count - 1; i >= 0; i--){var handler = additionalHandlersList[i];if (handler == null){var message = Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull(nameof(additionalHandlers));throw new InvalidOperationException(message);}// Checking for this allows us to catch cases where someone has tried to re-use a handler. That really won't// work the way you want and it can be tricky for callers to figure out.if (handler.InnerHandler != null){var message = Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid(nameof(DelegatingHandler.InnerHandler),nameof(DelegatingHandler),nameof(HttpMessageHandlerBuilder),Environment.NewLine,handler);throw new InvalidOperationException(message);}handler.InnerHandler = next;next = handler;}return next;}}
HttpMessageHandlerBuilder
构造器中有两个核心属性PrimaryHandler
和AdditionalHandlers
,细心的同学可以发现AdditionalHandlers
是一个IList<DelegatingHandler>
列表,也就是说可以HttpClient 可以添加多个DelegatingHandler
即多个HttpMessageHandler
消息处理Handler 但是只能有一个PrimaryHandler
Handler
同时HttpMessageHandlerBuilder
提供了一个抽象的Build
方法,还有一个CreateHandlerPipeline
方法,这个方法主要是把IList<DelegatingHandler>
和PrimaryHandler
构造成一个MessageHandler 链表结构(通过DelegatingHandler
的InnerHandler
属性进行连接起来)
2.4 ConfigurePrimaryHttpMessageHandler
public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Func<HttpMessageHandler> configureHandler){if (builder == null){throw new ArgumentNullException(nameof(builder));}if (configureHandler == null){throw new ArgumentNullException(nameof(configureHandler));}builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>{options.HttpMessageHandlerBuilderActions.Add(b => b.PrimaryHandler = configureHandler());});return builder;}
通过上面的HttpMessageHandlerBuilder
源代码分析ConfigurePrimaryHttpMessageHandler
方法主要是给Builder 中添加PrimaryHandler
消息Handler
2.5 DefaultHttpMessageHandlerBuilder
我们知道在services.AddHttpClient()
方法中会注册默认的DefaultHttpMessageHandlerBuilder
消息构造器方法,它继承DefaultHttpMessageHandlerBuilder
,那我们来看看它的源代码
internal class DefaultHttpMessageHandlerBuilder : HttpMessageHandlerBuilder{public DefaultHttpMessageHandlerBuilder(IServiceProvider services){Services = services;}private string _name;public override string Name{get => _name;set{if (value == null){throw new ArgumentNullException(nameof(value));}_name = value;}}public override HttpMessageHandler PrimaryHandler { get; set; } = new HttpClientHandler();public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>();public override IServiceProvider Services { get; }public override HttpMessageHandler Build(){if (PrimaryHandler == null){var message = Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler));throw new InvalidOperationException(message);}return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers);}
代码中Build
会去调用HttpMessageHandlerBuilder 的CreateHandlerPipeline
方法把HttpMessageHandler 构建成一个类似于链表的结构。到这里源代码已经分析完了,接下来我们来演示一个Demo,来证明上面的核心HttpMessageHandler 流程走向图
三、Demo演示证明
我们继续来看上面我的Demo代码:
services.AddHttpClient("test").ConfigurePrimaryHttpMessageHandler(provider =>{return new PrimaryHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new LogHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new Log2HttpMessageHandler(provider);});
代码中自定义了两个HttpMessageHandler
和一个PrimaryHttpMessageHandler
我们再来分别看看Log2HttpMessageHandler
、LogHttpMessageHandler
和PrimaryHttpMessageHandler
代码,代码很简单就是SendAsync
前后输出了Log信息,代码如下:自定义的PrimaryHttpMessageHandler
代码如下:
public class PrimaryHttpMessageHandler: DelegatingHandler{private IServiceProvider _provider;public PrimaryHttpMessageHandler(IServiceProvider provider){_provider = provider;InnerHandler = new HttpClientHandler();}protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){System.Console.WriteLine("PrimaryHttpMessageHandler Start Log");var response= await base.SendAsync(request, cancellationToken);System.Console.WriteLine("PrimaryHttpMessageHandler End Log");return response;}}
Log2HttpMessageHandler
代码如下:
public class Log2HttpMessageHandler : DelegatingHandler{private IServiceProvider _provider;public Log2HttpMessageHandler(IServiceProvider provider){_provider = provider;//InnerHandler = new HttpClientHandler();}protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){System.Console.WriteLine("LogHttpMessageHandler2 Start Log");var response=await base.SendAsync(request, cancellationToken);System.Console.WriteLine("LogHttpMessageHandler2 End Log");return response;}}
LogHttpMessageHandler
代码如下:
public class LogHttpMessageHandler : DelegatingHandler{private IServiceProvider _provider;public LogHttpMessageHandler(IServiceProvider provider){_provider = provider;//InnerHandler = new HttpClientHandler();}protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){System.Console.WriteLine("LogHttpMessageHandler Start Log");var response=await base.SendAsync(request, cancellationToken);System.Console.WriteLine("LogHttpMessageHandler End Log");return response;}}
三个自定义Handler 代码已经完成,我们继续添加调用代码,如下:
/// <summary>////// </summary>/// <param name="url"></param>/// <returns></returns>public async Task<string> GetBaiduAsync(string url){var client = _clientFactory.CreateClient("test");var result = await client.GetStringAsync(url);return result;}
现在我们运行访问接口,运行后的控制台Log 如下图:看到输出结果,大家有没有发现跟Asp.net core 中的中间件管道的运行图一样。
四、总结
HttpClient
中HttpMessageHandler
可以自定义多个,但是只能有一个PrimaryHttpMessageHandler
如果添加多个只会被最后面添加的给覆盖;添加的一系列Handler 构成一个链式管道模型,并且PrimaryHttpMessageHandler
主的消息Handler 是在管道的最外层,也就是管道模型中的最后一道Handler。使用场景:我们可以通过自定义的MessageHandler 来动态加载请求证书,通过数据库的一些信息,在自定义的Handler 中加载注入对应的证书,这样可以起到动态加载支付证书作用,同时可以SendAsync 之前或者之后做一些自己的验证等相关业务,大家只需要理解它们的用途,自然知道它的强大作用,今天就分享到这里。
往期精彩回顾
【.net core】电商平台升级之微服务架构应用实战
.Net Core微服务架构技术栈的那些事
Asp.Net Core 中IdentityServer4 授权中心之应用实战
Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式
Asp.Net Core 中IdentityServer4 授权流程及刷新Token
Asp.Net Core 中IdentityServer4 实战之 Claim详解
Asp.Net Core 中IdentityServer4 实战之角色授权详解
Asp.Net Core 中间件应用实战中你不知道的那些事
Asp.Net Core Filter 深入浅出的那些事-AOP
Asp.Net Core EndPoint 终结点路由工作原理解读
ASP.NET CORE 内置的IOC解读及使用
♥ 给个[在看],是对我最大的支持 ♥