天天看點

為IHttpClientFactory添加動态命名配置

某些時候我們需要為HttpClient動态配置一些東西, 例如證書等, 參考博問 如何使用IHttpClientFactory動态添加cer證書. 例如服務是一個回調服務, 而被回調方采用了自定義的https(即自定義證書).

上述是一些前情概要, 那麼接下來我們就來實作這個需求.

秒想到一個方法, 我們可以直接<code>new HttpClient()</code>, 在每一次要使用的時候都直接來一個, 簡單粗暴.

秒想到第二個方法, 又或者用一個<code>Dictionary&lt;string,HttpClient&gt;</code>根據名字緩存client對象.

但是前者性能是個問題,而且涉及到端口的占用釋放問題, 在調用量稍大的情況下得涼涼, 後者則是有已知的問題HttpClient對象沒法感覺dns的變更.

其他一些更不靠譜的方法還有: 使用代碼配置方式(<code>services.AddHttpClient("callback provider side").ConfigurePrimaryHttpMessageHandler()</code>)配置所有證書, 還有把所有證書都安裝的本機上并設定為信任證書.

那麼能除了上面這些不靠譜的方式(或者說有緻命缺陷的方式), 還有靠譜的麼, 那當然是有的, 例如運作時的動态配置實作方案.

是以, 接下來, 我來推薦 2 種方式式,就是我們的<code>IHttpMessageHandlerBuilderFilter</code>和<code>IPostConfigureOptions</code>.

針對如何為HttpClient對象添加證書, 官方文檔的實作是:使用證書和來自 IHttpClientFactory 的命名 HttpClient 實作 HttpClient 和 使用證書和 HttpClientHandler 實作 HttpClient, 但是在這裡顯然沒法解決我們的運作時配置的需求, 但是它給出了一條線索, 那就是命名配置. 它可以為我們的每一個不同的provider提供自定義配置. 隻要我們能為每一個不同的provider能提供運作時配置即可, 接下來就是源碼閱讀時間了:

下文中的所有代碼都來自netcore 3.1, 并且僅copy關鍵代碼, 完整代碼可以前往github檢視.

每次<code>CreateClient</code>出來的都是一個新的<code>HttpClient</code>執行個體

在<code>CreateHandler</code>中的<code>_activeHandlers</code>将為我們緩存我們的handler, 預設是2分鐘(定義在<code>HttpClientFactoryOptions.HandlerLifetime</code>)

這裡有一個知識點就是如果我的請求剛好在過期時間前一點點擷取到這個緩存的對象,就是有可能我目前的請求還在進行中, 但是2分鐘過去後這個handler就要被回收的. 那官方是如何替我們解決這個可能的bug的呢, 請檢視文章Cleaning up expired handlers, 我就不贅述了, 關鍵點在于用了一個<code>WeakReference</code>

<code>CreateHandlerEntry</code>方法則是真正的建立以及配置我們的handlers的地方.

從<code>IConfiguration</code>獲得一個<code>HttpClientFactoryOptions</code>對象

應用 <code>IHttpMessageHandlerBuilderFilter</code>

應用 <code>HttpMessageHandlerBuilderActions</code>

關鍵點代碼就是上面代碼中标記出來的<code>擴充點一</code> 和 <code>擴充點二</code>.

擴充點一: 需要注入适當的IHttpMessageHandlerBuilderFilter對象,就可以改寫<code>requiredService</code>對象, 也就可以實作我們要的運作時動态配置了.

擴充點二: 需要實作自定義的IConfiguration配置, 隻要<code>this._optionsMonitor.Get(name)</code>拿到的對象的<code>HttpMessageHandlerBuilderActions</code>屬性包含我們相應的改寫代碼即可.

為HttpClient的handler增加一個配置的filter, 針對符合的handlerBuilder增加一些自己的改寫邏輯.

我們在用HttpClient對象的時候産生的日志("Sending HTTP request......","Received HTTP response headers after......")就是由這個Filter特性注入的. 官方參考代碼:LoggingHttpMessageHandlerBuilderFilter

個人見解: 覺得在這個擴充點加這個業務不是特别的符合應用場景, 是以我建議在擴充點二做這個事情.

為什麼這裡注入的類型是<code>Microsoft.Extensions.Options.IPostConfigureOptions&lt;Microsoft.Extensions.Http.HttpClientFactoryOptions&gt;</code>, 是因為<code>OptionsFactory</code>它的構造函數需要的就是這個. 至于有關Configuration系統的擴充和源代碼在這裡就不在這裡展開了.

至于用它就簡單了

這樣子, 我們的這個運作時動态配置HttpClient就算完成了, 我也輕輕松松又水了一篇文章.

另外,有關<code>IHttpClientFactory</code>背後的故事可以檢視文章Exploring the code behind IHttpClientFactory in depth, 很完整的流程圖在配上代碼, 把它講解的清清楚楚.