ASP.NET Core 在 2.1 以後推出了具備彈性 HTTP 請求能力的 HttpClient 工廠類 HttpClientFactory。html
替換的初衷仍是簡單擺一下:
① using(var client = new HttpClient()) 調用的 Dispose() 方法並不會當即釋放底層 Socket 鏈接,新建 Socket 須要時間,致使在高併發場景下 Socket 耗盡。
② 基於 ① 不少人會想到使用單例或者靜態類構造 HttpClient 實例,可是這裏有一個坑,HttpClient 不會反應 DNS 的變動。git
HttpClientFactory 以模塊化、可命名、可配置、彈性方式重建了 HttpClient 的使用方式: 由 DI 框架注入 IHttpClientFactory 工廠;由工廠建立 HttpClient 並從內部的 Handler 池分配請求 Handler。github
HttpClient 可在 DI 框架中經過
IHttpCLientBuilder
對象配置 Policy 策略。算法
我一直對這種顛覆傳統 HttpClient 的代碼組織方式感到好奇,今天咱們帶着問題來探究一下新版 HttpClient 的實現。設計模式
一個完整的 HttpClient 包括三部分:數組
附加的 HttpMessageHandler 須要與核心 HttpMessageHandler 造成鏈式 Pipeline 關係,最終端點指向核心 HttpMessageHandler,
鏈表數據結構是 DelegatingHandler 關鍵類(包含 InnerHandler 鏈表節點指針)服務器
很明顯,HttpClientFactory 源碼的解讀分爲 2 部分,內心藏着僞代碼,帶着問題思考更香(手動狗頭)。數據結構
在 Startup.cs 文件開始配置要用到的 HttpClient併發
services.AddHttpClient("bce-request", x => x.BaseAddress = new Uri(Configuration.GetSection("BCE").GetValue<string>("BaseUrl"))) .ConfigurePrimaryHttpMessageHandler(_ => new BceAuthClientHandler() { AccessKey = Configuration.GetSection("BCE").GetValue<string>("AccessKey"), SerectAccessKey = Configuration.GetSection("BCE").GetValue<string>("SecretAccessKey"), AllowAutoRedirect = true, UseDefaultCredentials = true }) .SetHandlerLifetime(TimeSpan.FromHours(12)) .AddPolicyHandler(GetRetryPolicy(3));
配置過程充分體現了.NET Core 推崇的萬物皆服務,配置前移
的 DI 風格;
同對時 HttpClient 的基礎、配置均經過配置即委託
來完成app
Q1. 如何記錄以上配置?
微軟使用一個HttpClientFactoryOptions
對象來記錄 HttpClient 配置,這個套路是否是很熟悉?
AddHttpClient
擴展方法產生 HttpClientBuilder 對象ConfigurePrimaryHttpMessageHandler
擴展方法會將核心 Handler 插到 Options 對象的 HttpMessageHandlerBuilderActions 數組,做爲 Handlers 數組中的 PrimaryHandlerAddPolicyHandler
擴展方法也會將 PolicyHttpMessageHandler 插到 Options 對象的 HttpMessageHandlerBuilderActions 數組,可是做爲 AdditionHandler// An options class for configuring the default System.Net.Http.IHttpClientFactory public class HttpClientFactoryOptions { public HttpClientFactoryOptions(); // 一組用於配置HttpMessageHandlerBuilder的操做委託 public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } public IList<Action<HttpClient>> HttpClientActions { get; } public TimeSpan HandlerLifetime { get; set; } public bool SuppressHandlerScope { get; set; } }
顯而易見,後期建立 HttpClient 實例時會經過 name 找到對應的 Options,從中加載配置和 Handlers。
經過 IHttpClientFactory.CreateClient() 產生的 HttpClient 實例有一些內部行爲:
標準的 HttpClient(不帶 Policy 策略)除了 PrimaryHandler 以外,微軟給你附加了兩個 AdditionHandler:
以後將排序後的 AdditionHanders 數組與 PrimaryHandler 經過 DelegatingHandler 數據結構轉化爲鏈表, 末節點是 PrimaryHandler
輸出的日誌以下:
Q2. 微軟爲啥要增長外圍日誌 Handler?
這要結合 P1 給出的帶 Policy 策略的 HttpClient,帶 Policy 策略的 HttpClient 會在 AdditionHandlers 插入 PolicyHttpMessageHandler 來控制retry
、Circuit Breaker
,那麼就會構建這樣的 Handler Pipeline:
因此微軟會在 AdditionHandlers 數組最外圍提供一個業務含義的日誌 LogicalHandler,最內層固定 LoggingHttpHandler,這是否是很靠譜?
無圖無真相,請查看帶Policy策略
的 HttpClient 請求堆棧:
Q3. 何處強插、強行固定這兩個日誌 Handler?
微軟經過在 DI 環節注入默認的 LoggingHttpMessageHandlerBuilderFilter 來重排 Handler 的位置:
// 截取自LoggingHttpMessageHandlerBuilderFilter文件 public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next) { return (builder) => { next(builder); var loggerName = !string.IsNullOrEmpty(builder.Name) ? builder.Name : "Default"; // We want all of our logging message to show up as-if they are coming from HttpClient, // but also to include the name of the client for more fine-grained control. var outerLogger = _loggerFactory.CreateLogger($"System.Net.Http.HttpClient.{loggerName}.LogicalHandler"); var innerLogger = _loggerFactory.CreateLogger($"System.Net.Http.HttpClient.{loggerName}.ClientHandler"); var options = _optionsMonitor.Get(builder.Name); // The 'scope' handler goes first so it can surround everything. builder.AdditionalHandlers.Insert(0, new LoggingScopeHttpMessageHandler(outerLogger, options)); // We want this handler to be last so we can log details about the request after // service discovery and security happen. builder.AdditionalHandlers.Add(new LoggingHttpMessageHandler(innerLogger, options)); }; }
Q4. 建立 HttpClient 時,如何將 AdditionHandlers 和 PrimaryHandler 造成鏈式 Pipeline 關係 ?
protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable<DelegatingHandler> 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); } handler.InnerHandler = next; next = handler; } }
數組轉鏈表IReadOnlyList<DelegatingHandler>
的算法與 ASP.NET Core 框架的 Middleware 構建 Pipeline 一模一樣。
僞代碼表示實例建立過程:
DefaultHttpClientFactory.CreateClient()
--->構造函數由 DI 注入默認的 LoggingHttpMessageHandlerBuilderFilter
--->經過 Options.HttpMessageHandlerBuilderActions 拿到全部的 Handler
--->使用 LoggingHttpMessageHandlerBuilderFilter 強排 AdditionHandlers
--->建立 Handler 鏈式管道
--->用以上鍊式初始化 HttpClient 實例
--->從 Options.HttpClientActions 中提取對於 Httpclient 的基礎配置
--->返回一個基礎、HttpHandler 均正確配置的 HttpClient 實例
上述行爲依賴於 ASP.NETCor 框架在 DI 階段注入的幾個服務:
咱們探究System.Net.Http庫的目的:
學習精良的設計模式、理解默認的DI行爲;
默認DI行爲給咱們提供了擴展/改造 HttpClientFactory 的一個思路。
附贈有關應用 HttpClient 的三個瓜:
1. AllowAutoRedirect 屬性 : 控制請求收到重定向響應 能 發起跳轉請求, 默認爲 true
2. AutomaticDecompression枚舉: 支持對服務端response 壓縮的解碼
3. ServerCertificateCustomValidationCallback: 自定義對服務器證書的認證邏輯
原文出處:https://www.cnblogs.com/JulianHuang/p/12405973.html