.NET Core 3.0之深入原始碼理解HttpClientFactory(二)

艾心❤發表於2019-07-29
 

寫在前面

上一篇文章討論了通過在ConfigureServices中呼叫services.AddHttpClient()方法,並基於此進一步探討了DefaultHttpClientFactory是如何建立HttpClient例項和HttpMessageHandler例項的,並瞭解了DefaultHttpClientFactory內部維護者一個定時器和兩個HttpMessageHandler物件集合,以定期清理無效的 HttpMessageHandler物件,詳細的內容可以點選連結跳轉,接下來我會接著前一篇文章繼續展開相關討論。

詳細介紹

HttpMessageHandlerBuilder

該類是一個抽象類,起到生成器的作用,可用於用於配置HttpMessageHandler例項。HttpMessageHandlerBuilder會在ServiceCollection中被註冊為Transient服務。呼叫方要為每個要建立的HttpMessageHandler例項檢索一個新例項。實現者應該確保每個例項都只使用一次。

HttpMessageHandlerBuilder裡面有三個比較重要的屬性:

   1:  /// <summary>
   2:  /// 主HttpMessageHandler例項
   3:  /// </summary>
   4:  public abstract HttpMessageHandler PrimaryHandler { get; set; }
   5:   
   6:  /// <summary>
   7:  /// 這個是一個附加例項,用於配置HttpClient管道
   8:  /// </summary>
   9:  public abstract IList<DelegatingHandler> AdditionalHandlers { get; }
  10:   
  11:  /// <summary>
  12:  /// 可用於從依賴項注入容器解析服務的IServiceProvider
  13:  /// </summary>
  14:  public virtual IServiceProvider Services { get; }

這三個屬性意味著每個HttpMessageHandlerBuilder都需要維護自身的HttpMessageHandler例項和管道。

其內部還有一個抽象方法:

   1:  public abstract HttpMessageHandler Build();

當然,內部最核心的方法就是管道的建立過程了,需要傳入主派生類自身的HttpMessageHandler和管道列表物件。它會將primaryHandler例項付給管道列表的第一個Item的InnerHandler,其他物件會依此後移,這也為我們自定義HttpMessageHandler(各種中介軟體)提供了無限可能。

相關實現如下:

   1:  var next = primaryHandler;
   2:  for (var i = additionalHandlersList.Count - 1; i >= 0; i--)
   3:  {
   4:      var handler = additionalHandlersList[i];
   5:      if (handler == null)
   6:      {
   7:          var message = Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull(nameof(additionalHandlers));
   8:          throw new InvalidOperationException(message);
   9:      }
  10:   
  11:      if (handler.InnerHandler != null)
  12:      {
  13:          var message = Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid(
  14:              nameof(DelegatingHandler.InnerHandler),
  15:              nameof(DelegatingHandler),
  16:              nameof(HttpMessageHandlerBuilder),
  17:              Environment.NewLine,
  18:              handler);
  19:          throw new InvalidOperationException(message);
  20:      }
  21:   
  22:      handler.InnerHandler = next;
  23:      next = handler;
  24:  }

接下來我們看一下HttpMessageHandlerBuilder一個派生類DefaultHttpMessageHandlerBuilder,其建構函式會傳入IServiceProvider例項,我們的自定義操作也可以參照這個類。

關於Build方法的實現如下,比較簡單主要是呼叫了CreateHandlerPipeline方法:

   1:  public override HttpMessageHandler Build()
   2:  {
   3:      if (PrimaryHandler == null)
   4:      {
   5:          var message = Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler));
   6:          throw new InvalidOperationException(message);
   7:      }
   8:      
   9:      return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers);
  10:  }

ITypedHttpClientFactory

這是一個抽象工廠,該元件可以使用給定邏輯名稱的自定義配置建立型別化HttpClient例項,與命名方式建立HttpClient具有相同的的功能。型別化客戶端可能用於單個後端終結點,並封裝此終結點的所有處理邏輯。 另一個優勢是它們使用 DI 被注入到應用中需要的位置,下一篇文章會再次討論相關功能。

我們首先看一下呼叫方式:

   1:  public static IHttpClientBuilder AddHttpClient<TClient>(this IServiceCollection services)
   2:      where TClient : class
   3:  {
   4:      if (services == null)
   5:      {
   6:          throw new ArgumentNullException(nameof(services));
   7:      }
   8:   
   9:      AddHttpClient(services);
  10:   
  11:      var name = TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false);
  12:      var builder = new DefaultHttpClientBuilder(services, name);
  13:      builder.AddTypedClient<TClient>();
  14:      return builder;
  15:  }

可以看出此處的呼叫與普通的HttpClient沒有什麼太大區別,只是增加了一個泛型標記,而且該型別沒有特殊的要求,只要是個類就行。其內部依然呼叫AddHttpClient(services),但它呼叫了另一個擴充套件方法,如下所示:

   1:  public static IHttpClientBuilder AddTypedClient<TClient>(this IHttpClientBuilder builder)
   2:      where TClient : class
   3:  {
   4:      if (builder == null)
   5:      {
   6:          throw new ArgumentNullException(nameof(builder));
   7:      }
   8:   
   9:      builder.Services.AddTransient<TClient>(s =>
  10:      {
  11:          var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
  12:          var httpClient = httpClientFactory.CreateClient(builder.Name);
  13:   
  14:          var typedClientFactory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
  15:          return typedClientFactory.CreateClient(httpClient);
  16:      });
  17:   
  18:      return builder;
  19:  }

可以看到最終的程式碼呼叫了ITypedHttpClientFactory的CreateClient方法,Microsoft.Extensions.Http包中有一個預設的ITypedHttpClientFactory派生類,DefaultTypedHttpClientFactory<TClient>,該類提供了了建構函式用於接收IServiceProvider例項,以及一個內部類宣告的快取物件,該物件十分重要,它被註冊為singleton型別,已達到全域性使用,並可以充當相關例項啟用時的物件池。它也允許它的外部類註冊為transient,這樣它就不會在應用根服務提供程式上被關掉了。

相關程式碼如下:

   1:  public TClient CreateClient(HttpClient httpClient)
   2:  {
   3:      if (httpClient == null)
   4:      {
   5:          throw new ArgumentNullException(nameof(httpClient));
   6:      }
   7:   
   8:      return (TClient)_cache.Activator(_services, new object[] { httpClient });
   9:  }

內部快取物件:

   1:  public class Cache
   2:  {
   3:      private readonly static Func<ObjectFactory> _createActivator = () => ActivatorUtilities.CreateFactory(typeof(TClient), new Type[] { typeof(HttpClient), });
   4:   
   5:      private ObjectFactory _activator;
   6:      private bool _initialized;
   7:      private object _lock;
   8:   
   9:      public ObjectFactory Activator => LazyInitializer.EnsureInitialized(
  10:          ref _activator, 
  11:          ref _initialized, 
  12:          ref _lock, 
  13:          _createActivator);
  14:  }

最後我們看一下原始碼中提供的範例:

   1:  class ExampleClient
   2:  {
   3:      private readonly HttpClient _httpClient;
   4:      private readonly ILogger _logger;
   5:      // typed clients can use constructor injection to access additional services
   6:      public ExampleClient(HttpClient httpClient, ILogger<ExampleClient> logger)
   7:      {
   8:          _httpClient = httpClient;
   9:          _logger = logger;     
  10:      }
  11:      // typed clients can expose the HttpClient for application code to call directly
  12:      public HttpClient HttpClient => _httpClient;
  13:      // typed clients can also define methods that abstract usage of the HttpClient
  14:      public async Task SendHelloRequest()
  15:      {
  16:          var response = await _httpClient.GetAsync("/helloworld");
  17:          response.EnsureSuccessStatusCode();
  18:      }
  19:  }
  20:  //This sample shows how to consume a typed client from an ASP.NET Core middleware.
  21:  public void Configure(IApplicationBuilder app, ExampleClient exampleClient)
  22:  {
  23:      app.Run(async (context) =>
  24:      {
  25:          var response = await _exampleClient.GetAsync("/helloworld");
  26:          await context.Response.WriteAsync("Remote server said: ");
  27:          await response.Content.CopyToAsync(context.Response.Body);
  28:      });
  29:  }
  30:  //This sample shows how to consume a typed client from an ASP.NET Core MVC Controller.
  31:  public class HomeController : ControllerBase(IApplicationBuilder app, ExampleClient exampleClient)
  32:  {
  33:      private readonly ExampleClient _exampleClient;
  34:      public HomeController(ExampleClient exampleClient)
  35:      {
  36:          _exampleClient = exampleClient;
  37:      }
  38:      public async Task<IActionResult> Index()
  39:      {
  40:          var response = await _exampleClient.GetAsync("/helloworld");
  41:          var text = await response.Content.ReadAsStringAsync();
  42:          return Content("Remote server said: " + text, "text/plain");
  43:      };
  44:  }

相關文章