.net core HttpClient 使用之消息管道解析(二)
一、前言
前面分享了 .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解讀及使用
??給個[在看],是對我最大的支持??
總結
以上是生活随笔為你收集整理的.net core HttpClient 使用之消息管道解析(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Web页面适配移动端方案研究
- 下一篇: List的扩容机制,你真的明白吗?