HttpClientFactory的套路,你知多少?

背景

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 包括三部分:數組

  • 基礎業務配置: 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 的基礎、配置均經過配置即委託來完成app

Q1. 如何記錄以上配置?

微軟使用一個HttpClientFactoryOptions對象來記錄 HttpClient 配置,這個套路是否是很熟悉?

  • 經過 DI 框架的AddHttpClient擴展方法產生 HttpClientBuilder 對象
  • HttpClientBuilder 對象的ConfigurePrimaryHttpMessageHandler擴展方法會將核心 Handler 插到 Options 對象的 HttpMessageHandlerBuilderActions 數組,做爲 Handlers 數組中的 PrimaryHandler
  • HttpClientBuilder 對象的AddPolicyHandler擴展方法也會將 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。

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 來控制retryCircuit 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 階段注入的幾個服務:

  • DefaultHttpClientFactory
  • LoggingHttpMessageHandlerBuilderFilter:過濾並強排 AdditionHandler
  • DefaultHttpMessageHandlerBuilder: Handler數組轉鏈表

咱們探究System.Net.Http庫的目的:
學習精良的設計模式、理解默認的DI行爲;
默認DI行爲給咱們提供了擴展/改造 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

 

 

附贈有關應用 HttpClient 的三個瓜:

 1.  AllowAutoRedirect 屬性 : 控制請求收到重定向響應 能 發起跳轉請求, 默認爲 true

 2.  AutomaticDecompression枚舉: 支持對服務端response 壓縮的解碼

 3. ServerCertificateCustomValidationCallback: 自定義對服務器證書的認證邏輯

原文出處:https://www.cnblogs.com/JulianHuang/p/12405973.html

相關文章
相關標籤/搜索