我們可以通過自定義的MessageHandler 來動态加載請求證書,通過資料庫的一些資訊,在自定義的Handler 中加載注入對應的證書,這樣可以起到動态加載支付證書作用,同時可以SendAsync 之前或者之後做一些自己的驗證等相關業務,大家隻需要了解它們的用途,自然知道它的強大作用,今天就分享到這裡
一、前言
前面分享了 .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
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 之前或者之後做一些自己的驗證等相關業務,大家隻需要了解它們的用途,自然知道它的強大作用,今天就分享到這裡
如果您認為這篇文章還不錯或者有所收獲,您可以點選右下角的【推薦】按鈕精神支援,因為這種支援是我繼續寫作,分享的最大動力!
作者:Jlion
聲明:原創部落格請在轉載時保留原文連結或者在文章開頭加上本人部落格位址,如發現錯誤,歡迎批評指正。凡是轉載于本人的文章,不能設定打賞功能,如有特殊需求請與本人聯系!
為了更好的維護開源項目以及技術交流,特意建立了一個交流群,群号:1083147206 有興趣者可以加入交流
如果您覺的不錯,請微信掃碼關注 【dotNET 博士】公衆号,後續給您帶來更精彩的分享