天天看點

.NET Core 3.0之深入源碼了解HttpClientFactory(二)

.NET Core 3.0之深入源碼了解HttpClientFactory(二)

寫在前面

上一篇文章讨論了通過在ConfigureServices中調用services.AddHttpClient()方法,并基于此進一步探讨了DefaultHttpClientFactory是如何建立HttpClient執行個體和HttpMessageHandler執行個體的,并了解了DefaultHttpClientFactory内部維護者一個定時器和兩個HttpMessageHandler對象集合,以定期清理無效的 HttpMessageHandler對象,詳細的内容可以點選連結跳轉,接下來我會接着前一篇文章繼續展開相關讨論。

詳細介紹

HttpMessageHandlerBuilder

該類是一個抽象類,起到生成器的作用,可用于用于配置HttpMessageHandler執行個體。HttpMessageHandlerBuilder會在ServiceCollection中被注冊為Transient服務。調用方要為每個要建立的HttpMessageHandler執行個體檢索一個新執行個體。實作者應該確定每個執行個體都隻使用一次。

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

1: ///

2: /// 主HttpMessageHandler執行個體

3: ///

4: public abstract HttpMessageHandler PrimaryHandler { get; set; }

5:

6: ///

7: /// 這個是一個附加執行個體,用于配置HttpClient管道

8: ///

9: public abstract IList AdditionalHandlers { get; }

10:

11: ///

12: /// 可用于從依賴項注入容器解析服務的IServiceProvider

13: ///

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: }

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(this IServiceCollection services)

2: where TClient : class

4: if (services == null)

5: {

6: throw new ArgumentNullException(nameof(services));

8:

9: AddHttpClient(services);

11: var name = TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false);

12: var builder = new DefaultHttpClientBuilder(services, name);

13: builder.AddTypedClient();

14: return builder;

15: }

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

1: public static IHttpClientBuilder AddTypedClient(this IHttpClientBuilder builder)

4: if (builder == null)

6: throw new ArgumentNullException(nameof(builder));

9: builder.Services.AddTransient(s =>

10: {

11: var httpClientFactory = s.GetRequiredService();

12: var httpClient = httpClientFactory.CreateClient(builder.Name);

13:

14: var typedClientFactory = s.GetRequiredService>();

15: return typedClientFactory.CreateClient(httpClient);

16: });

17:

18: return builder;

19: }

可以看到最終的代碼調用了ITypedHttpClientFactory的CreateClient方法,Microsoft.Extensions.Http包中有一個預設的ITypedHttpClientFactory派生類,DefaultTypedHttpClientFactory,該類提供了了構造函數用于接收IServiceProvider執行個體,以及一個内部類聲明的緩存對象,該對象十分重要,它被注冊為singleton類型,已達到全局使用,并可以充當相關執行個體激活時的對象池。它也允許它的外部類注冊為transient,這樣它就不會在應用根服務提供程式上被關掉了。

相關代碼如下:

1: public TClient CreateClient(HttpClient httpClient)

3: if (httpClient == null)

5: throw new ArgumentNullException(nameof(httpClient));

6: }

7:

8: return (TClient)_cache.Activator(_services, new object[] { httpClient });

9: }

内部緩存對象:

1: public class Cache

3: private readonly static Func _createActivator = () => ActivatorUtilities.CreateFactory(typeof(TClient), new Type[] { typeof(HttpClient), });

4:

5: private ObjectFactory _activator;

6: private bool _initialized;

7: private object _lock;

9: public ObjectFactory Activator => LazyInitializer.EnsureInitialized(

10: ref _activator,

11: ref _initialized,

12: ref _lock,

13: _createActivator);

14: }

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

1: class ExampleClient

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 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: }

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 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: }

以上為本篇文章的主要内容,希望大家多提意見,如果喜歡記得點個推薦哦

作者: 艾心

出處:

https://www.cnblogs.com/edison0621/

繼續閱讀