HttpClientFactory的套路,你知多少?
背景
ASP.NET Core 在 2.1 之后推出了具有彈性 HTTP 請求能力的 HttpClient 工廠類 HttpClientFactory。
替換的初衷還是簡單擺一下:
① using(var client = new HttpClient()) 調用的 Dispose() 方法并不會立即釋放底層 Socket 連接,新建 Socket 需要時間,導致在高并發場景下 Socket 耗盡。
② 基于 ① 很多人會想到使用單例或者靜態類構造 HttpClient 實例,但是這里有一個坑,HttpClient 不會反應 DNS 的變更。
HttpClientFactory 以模塊化、可命名、可配置、彈性方式重建了 HttpClient 的使用方式:由 DI 框架注入 IHttpClientFactory 工廠;由工廠創建 HttpClient 并從內部的 Handler 池分配請求 Handler。
HttpClient 可在 DI 框架中通過IHttpCLientBuilder對象配置 Policy 策略。
我一直對這種顛覆傳統 HttpClient 的代碼組織方式感到好奇,今天我們帶著問題來探究一下新版 HttpClient 的實現。
與碼無瓜
一個完整的 HttpClient 包括三部分:
基礎業務配置:BaseAddress、DefaultRequestHeaders、DefaultProxy、TimeOut.....
核心 MessageHandler:負責核心的業務請求
[可選的]附加 HttpMessageHandler
附加的 HttpMessageHandler 需要與核心 HttpMessageHandler 形成鏈式 Pipeline 關系,最終端點指向核心 HttpMessageHandler,
鏈表數據結構是 DelegatingHandler 關鍵類(包含 InnerHandler 鏈表節點指針)
刨瓜問底
很明顯,HttpClientFactory 源碼的解讀分為 2 部分,心里藏著偽代碼,帶著問題思考更香(手動狗頭)。
P1. 構建 HttpClient
在 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 的基礎、配置均通過配置即委托來完成
Q1. 如何記錄以上配置?
微軟使用一個HttpClientFactoryOptions對象來記錄 HttpClient 配置,這個套路是不是很熟悉?
通過 DI 框架的AddHttpClient擴展方法產生 HttpClientBuilder 對象
HttpClientBuilder 對象的ConfigurePrimaryHttpMessageHandler擴展方法會將核心 Handler 插到 Options 對象的 HttpMessageHandlerBuilderActions 數組,作為 Handlers 數組中的 PrimaryHandler
HttpClientBuilder 對象的AddPolicyHandler擴展方法也會將 PolicyHttpMessageHandler 插到 Options 對象的 HttpMessageHandlerBuilderActions 數組,但是作為 AdditionHandler
顯而易見,后期創建 HttpClient 實例時會通過 name 找到對應的 Options,從中加載配置和 Handlers。
P2. 初始化 HttpClient 實例
通過 IHttpClientFactory.CreateClient() 產生的 HttpClient 實例有一些內部行為:
標準的 HttpClient(不帶 Policy 策略)除了 PrimaryHandler 之外,微軟給你附加了兩個 AdditionHandler:
LoggingScopeHttpMessageHandler:最外圍 Logical 日志
LoggingHttpMessageHandler:核心 Http 請求日志
之后將排序后的 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 的位置:
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 拿到所有的 Handlers
--->使用 LoggingHttpMessageHandlerBuilderFilter 強排 AdditionHandlers
--->創建 Handler 鏈式管道
--->用以上鏈式初始化 HttpClient 實例
--->從 Options.HttpClientActions 中提取對于 Httpclient 的基礎配置
--->返回一個基礎、HttpHandler 均正確配置的 HttpClient 實例
上述行為依賴于 ASP.NETCor 框架在 DI 階段注入的幾個服務:
DefaultHttpClientFactory
LoggingHttpMessageHandlerBuilderFilter:過濾并強排 AdditionHandlers
DefaultHttpMessageHandlerBuilder:Handler數組轉鏈表
我們探究System.Net.Http庫的目的:
學習精良的設計模式、理解默認的DI行為;
默認DI行為給我們提供了擴展/改造 HttpClientFactory 的一個思路:HttpClientFactory日志不好用,自己擴展一個?
https://github.com/dotnet/extensions/blob/master/src/HttpClientFactory/Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs
https://github.com/dotnet/extensions/blob/master/src/HttpClientFactory/Http/src/DefaultHttpClientFactory.cs
推薦閱讀
●?程序員應對瀏覽器同源策略的姿勢
●?臨近年關,修復ASP.NET Core因瀏覽器內核版本引發的單點登錄故障
●?ASP.NET Core跨平臺技術內幕
●?TPL Dataflow組件應對高并發,低延遲要求
●?實例解讀Docker Swarm
●?基于docker-compose的Gitlab CI/CD實踐&排坑指南
總結
以上是生活随笔為你收集整理的HttpClientFactory的套路,你知多少?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个迷你ASP.NET Core框架的实
- 下一篇: .NET Core开发实战(第16课:选