前面分享了 .net core HttpClient 使用之掉坑解析(一),今天來分享自定義消息處理HttpMessageHandler
和PrimaryHttpMessageHandler
的使用場景和區別html
先貼上一張核心MessageHandler 管道模型的流程圖,圖以下:
HttpClient 中的HttpMessageHandler
負責主要核心的業務,HttpMessageHandler
是由MessageHandler 鏈表結構組成,造成一個消息管道模式;具體咱們一塊兒來看看源代碼react
再閱讀源代碼的時候咱們先來看下下面注入HttpClient
的Demo 代碼,代碼以下:ios
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
的擴展方法。git
AddHttpMessageHandler
咱們來看看HttpClientBuilderExtensions
中的其中一個AddHttpMessageHandler
擴展方法,代碼以下:github
/// <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:app
public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();
提供了HttpMessageHandlerBuilder
HttpMessageHandler 的構造器列表對象,故,經過AddHttpMessageHandler
能夠添加一系列的消息構造器方法對象
咱們再來看看這個消息構造器類,核心部分,代碼以下:async
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
Handleride
同時HttpMessageHandlerBuilder
提供了一個抽象的Build
方法,還有一個CreateHandlerPipeline
方法,這個方法主要是把IList<DelegatingHandler>
和PrimaryHandler
構形成一個MessageHandler 鏈表結構(經過DelegatingHandler
的InnerHandler
屬性進行鏈接起來)ui
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
咱們知道在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代碼:
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 以前或者以後作一些本身的驗證等相關業務,你們只須要理解它們的用途,天然知道它的強大做用,今天就分享到這裏