HttpClientFactory的套路,你知多少?
背景
ASP.NET Core 在 2.1 之后推出了具有彈性 HTTP 請(qǐng)求能力的 HttpClient 工廠類(lèi) HttpClientFactory。
替換的初衷還是簡(jiǎn)單擺一下:
① using(var client = new HttpClient()) 調(diào)用的 Dispose() 方法并不會(huì)立即釋放底層 Socket 連接,新建 Socket 需要時(shí)間,導(dǎo)致在高并發(fā)場(chǎng)景下 Socket 耗盡。
② 基于 ① 很多人會(huì)想到使用單例或者靜態(tài)類(lèi)構(gòu)造 HttpClient 實(shí)例,但是這里有一個(gè)坑,HttpClient 不會(huì)反應(yīng) DNS 的變更。
HttpClientFactory 以模塊化、可命名、可配置、彈性方式重建了 HttpClient 的使用方式:由 DI 框架注入 IHttpClientFactory 工廠;由工廠創(chuàng)建 HttpClient 并從內(nèi)部的 Handler 池分配請(qǐng)求 Handler。
HttpClient 可在 DI 框架中通過(guò)IHttpCLientBuilder對(duì)象配置 Policy 策略。
我一直對(duì)這種顛覆傳統(tǒng) HttpClient 的代碼組織方式感到好奇,今天我們帶著問(wèn)題來(lái)探究一下新版 HttpClient 的實(shí)現(xiàn)。
與碼無(wú)瓜
一個(gè)完整的 HttpClient 包括三部分:
基礎(chǔ)業(yè)務(wù)配置:BaseAddress、DefaultRequestHeaders、DefaultProxy、TimeOut.....
核心 MessageHandler:負(fù)責(zé)核心的業(yè)務(wù)請(qǐng)求
[可選的]附加 HttpMessageHandler
附加的 HttpMessageHandler 需要與核心 HttpMessageHandler 形成鏈?zhǔn)?Pipeline 關(guān)系,最終端點(diǎn)指向核心 HttpMessageHandler,
鏈表數(shù)據(jù)結(jié)構(gòu)是 DelegatingHandler 關(guān)鍵類(lèi)(包含 InnerHandler 鏈表節(jié)點(diǎn)指針)
刨瓜問(wèn)底
很明顯,HttpClientFactory 源碼的解讀分為 2 部分,心里藏著偽代碼,帶著問(wèn)題思考更香(手動(dòng)狗頭)。
P1. 構(gòu)建 HttpClient
在 Startup.cs 文件開(kāi)始配置要用到的 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));配置過(guò)程充分體現(xiàn)了.NET Core 推崇的萬(wàn)物皆服務(wù),配置前移的 DI 風(fēng)格;
同對(duì)時(shí) HttpClient 的基礎(chǔ)、配置均通過(guò)配置即委托來(lái)完成
Q1. 如何記錄以上配置?
微軟使用一個(gè)HttpClientFactoryOptions對(duì)象來(lái)記錄 HttpClient 配置,這個(gè)套路是不是很熟悉?
通過(guò) DI 框架的AddHttpClient擴(kuò)展方法產(chǎn)生 HttpClientBuilder 對(duì)象
HttpClientBuilder 對(duì)象的ConfigurePrimaryHttpMessageHandler擴(kuò)展方法會(huì)將核心 Handler 插到 Options 對(duì)象的 HttpMessageHandlerBuilderActions 數(shù)組,作為 Handlers 數(shù)組中的 PrimaryHandler
HttpClientBuilder 對(duì)象的AddPolicyHandler擴(kuò)展方法也會(huì)將 PolicyHttpMessageHandler 插到 Options 對(duì)象的 HttpMessageHandlerBuilderActions 數(shù)組,但是作為 AdditionHandler
顯而易見(jiàn),后期創(chuàng)建 HttpClient 實(shí)例時(shí)會(huì)通過(guò) name 找到對(duì)應(yīng)的 Options,從中加載配置和 Handlers。
P2. 初始化 HttpClient 實(shí)例
通過(guò) IHttpClientFactory.CreateClient() 產(chǎn)生的 HttpClient 實(shí)例有一些內(nèi)部行為:
標(biāo)準(zhǔn)的 HttpClient(不帶 Policy 策略)除了 PrimaryHandler 之外,微軟給你附加了兩個(gè) AdditionHandler:
LoggingScopeHttpMessageHandler:最外圍 Logical 日志
LoggingHttpMessageHandler:核心 Http 請(qǐng)求日志
之后將排序后的 AdditionHanders 數(shù)組與 PrimaryHandler 通過(guò) DelegatingHandler 數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為鏈表, 末節(jié)點(diǎn)是 PrimaryHandler
輸出的日志如下:
Q2. 微軟為啥要增加外圍日志 Handler?
這要結(jié)合 P1 給出的帶 Policy 策略的 HttpClient,帶 Policy 策略的 HttpClient 會(huì)在 AdditionHandlers 插入 PolicyHttpMessageHandler 來(lái)控制retry、Circuit Breaker,那么就會(huì)構(gòu)建這樣的 Handler Pipeline:
所以微軟會(huì)在 AdditionHandlers 數(shù)組最外圍提供一個(gè)業(yè)務(wù)含義的日志 LogicalHandler,最內(nèi)層固定 LoggingHttpHandler,這是不是很靠譜?
無(wú)圖無(wú)真相,請(qǐng)查看帶Policy策略的 HttpClient 請(qǐng)求堆棧:
Q3. 何處強(qiáng)插、強(qiáng)行固定這兩個(gè)日志 Handler?
微軟通過(guò)在 DI 環(huán)節(jié)注入默認(rèn)的 LoggingHttpMessageHandlerBuilderFilter 來(lái)重排 Handler 的位置:
Q4. 創(chuàng)建 HttpClient 時(shí),如何將 AdditionHandlers 和 PrimaryHandler 形成鏈?zhǔn)?Pipeline 關(guān)系 ?
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;} }數(shù)組轉(zhuǎn)鏈表IReadOnlyList<DelegatingHandler>的算法與 ASP.NET Core 框架的 Middleware 構(gòu)建 Pipeline 如出一轍。
總結(jié)
偽代碼演示實(shí)例創(chuàng)建過(guò)程:
DefaultHttpClientFactory.CreateClient()
--->構(gòu)造函數(shù)由 DI 注入默認(rèn)的 LoggingHttpMessageHandlerBuilderFilter
--->通過(guò) Options.HttpMessageHandlerBuilderActions 拿到所有的 Handlers
--->使用 LoggingHttpMessageHandlerBuilderFilter 強(qiáng)排 AdditionHandlers
--->創(chuàng)建 Handler 鏈?zhǔn)焦艿?br />--->用以上鏈?zhǔn)匠跏蓟?HttpClient 實(shí)例
--->從 Options.HttpClientActions 中提取對(duì)于 Httpclient 的基礎(chǔ)配置
--->返回一個(gè)基礎(chǔ)、HttpHandler 均正確配置的 HttpClient 實(shí)例
上述行為依賴(lài)于 ASP.NETCor 框架在 DI 階段注入的幾個(gè)服務(wù):
DefaultHttpClientFactory
LoggingHttpMessageHandlerBuilderFilter:過(guò)濾并強(qiáng)排 AdditionHandlers
DefaultHttpMessageHandlerBuilder:Handler數(shù)組轉(zhuǎn)鏈表
我們探究System.Net.Http庫(kù)的目的:
學(xué)習(xí)精良的設(shè)計(jì)模式、理解默認(rèn)的DI行為;
默認(rèn)DI行為給我們提供了擴(kuò)展/改造 HttpClientFactory 的一個(gè)思路:HttpClientFactory日志不好用,自己擴(kuò)展一個(gè)?
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
推薦閱讀
●?程序員應(yīng)對(duì)瀏覽器同源策略的姿勢(shì)
●?臨近年關(guān),修復(fù)ASP.NET Core因?yàn)g覽器內(nèi)核版本引發(fā)的單點(diǎn)登錄故障
●?ASP.NET Core跨平臺(tái)技術(shù)內(nèi)幕
●?TPL Dataflow組件應(yīng)對(duì)高并發(fā),低延遲要求
●?實(shí)例解讀Docker Swarm
●?基于docker-compose的Gitlab CI/CD實(shí)踐&排坑指南
總結(jié)
以上是生活随笔為你收集整理的HttpClientFactory的套路,你知多少?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 一个迷你ASP.NET Core框架的实
- 下一篇: .NET Core开发实战(第16课:选