写在前面

上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是如何创建HttpClient实例和HttpMessageHandler实例的,并了解了DefaultHttpClientFactory内部维护者一个定时器和两个HttpMessageHandler对象集合,以定期清理无效的 HttpMessageHandler对象,详细的内容可以点击链接跳转,接下来我会接着前一篇文章继续展开相关讨论。

详细介绍

HttpMessageHandlerBuilder

该类是一个抽象类,起到生成器的作用,可用于用于配置HttpMessageHandler实例。HttpMessageHandlerBuilder会在ServiceCollection中被注册为Transient服务。调用方要为每个要创建的HttpMessageHandler实例检索一个新实例。实现者应该确保每个实例都只使用一次。

HttpMessageHandlerBuilder里面有三个比较重要的属性:

  1. 1: /// <summary>
  1. 2: /// 主HttpMessageHandler实例
  1. 3: /// </summary>
  1. 4: public abstract HttpMessageHandler PrimaryHandler { get; set; }
  1. 5:  
  1. 6: /// <summary>
  1. 7: /// 这个是一个附加实例,用于配置HttpClient管道
  1. 8: /// </summary>
  1. 9: public abstract IList<DelegatingHandler> AdditionalHandlers { get; }
  1. 10:  
  1. 11: /// <summary>
  1. 12: /// 可用于从依赖项注入容器解析服务的IServiceProvider
  1. 13: /// </summary>
  1. 14: public virtual IServiceProvider Services { get; }

这三个属性意味着每个HttpMessageHandlerBuilder都需要维护自身的HttpMessageHandler实例和管道。

其内部还有一个抽象方法:

  1. 1: public abstract HttpMessageHandler Build();

当然,内部最核心的方法就是管道的创建过程了,需要传入主派生类自身的HttpMessageHandler和管道列表对象。它会将primaryHandler实例付给管道列表的第一个Item的InnerHandler,其他对象会依此后移,这也为我们自定义HttpMessageHandler(各种中间件)提供了无限可能。

相关实现如下:

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

接下来我们看一下HttpMessageHandlerBuilder一个派生类DefaultHttpMessageHandlerBuilder,其构造函数会传入IServiceProvider实例,我们的自定义操作也可以参照这个类。

关于Build方法的实现如下,比较简单主要是调用了CreateHandlerPipeline方法:

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

ITypedHttpClientFactory

这是一个抽象工厂,该组件可以使用给定逻辑名称的自定义配置创建类型化HttpClient实例,与命名方式创建HttpClient具有相同的的功能。类型化客户端可能用于单个后端终结点,并封装此终结点的所有处理逻辑。 另一个优势是它们使用 DI 被注入到应用中需要的位置,下一篇文章会再次讨论相关功能。

我们首先看一下调用方式:

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

可以看出此处的调用与普通的HttpClient没有什么太大区别,只是增加了一个泛型标记,而且该类型没有特殊的要求,只要是个类就行。其内部依然调用AddHttpClient(services),但它调用了另一个扩展方法,如下所示:

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

可以看到最终的代码调用了ITypedHttpClientFactory的CreateClient方法,Microsoft.Extensions.Http包中有一个默认的ITypedHttpClientFactory派生类,DefaultTypedHttpClientFactory<TClient>,该类提供了了构造函数用于接收IServiceProvider实例,以及一个内部类声明的缓存对象,该对象十分重要,它被注册为singleton类型,已达到全局使用,并可以充当相关实例激活时的对象池。它也允许它的外部类注册为transient,这样它就不会在应用根服务提供程序上被关掉了。

相关代码如下:

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

内部缓存对象:

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

最后我们看一下源码中提供的范例:

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

.NET Core 3.0之深入源码理解HttpClientFactory(二)的更多相关文章

  1. .NET Core 3.0之深入源码理解Host(二)

      写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...

  2. .NET Core 3.0之深入源码理解Configuration(二)

      文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义 ...

  3. .NET Core 3.0之深入源码理解HttpClientFactory(一)

    写在前面 创建HttpClient实例的时候,在内部会创建HttpMessageHandler链,我们知道HttpMessageHandler是负责建立连接的抽象处理程序,所以HttpClient的维 ...

  4. .NET Core 3.0之深入源码理解Startup的注册及运行

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  5. .NET Core 3.0之深入源码理解Configuration(一)

    Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...

  6. .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)

      写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...

  7. .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)

      前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...

  8. .NET Core 3.0之深入源码理解ObjectPool(一)

    写在前面 对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销. 它使用取用/归还的操作模式,并重复执行这些 ...

  9. .NET Core 3.0之深入源码理解HealthCheck(一)

    写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...

随机推荐

  1. Enter passphrase

    提示“Enter passphrase for key /root/.ssh/id_rsa.pub”让输入私钥,可不论输与不输都不能直接登录 解决方法: 在本地执行: eval `ssh-agent` ...

  2. js中新增动态属性

    var cc = 'hell' var mm = { [cc](){ alert(33) } } mm.hell() 使用的就是数组形式

  3. 简单的scrapy实例

    前天实验室的学长要求写一个简单的scrapy工程出来,之前也多少看了点scrapy的知识,但始终没有太明白,刚好趁着这个机会,加深一下对scrapy工作流程的理解.由于临近期末,很多作业要做(其实.. ...

  4. 设计和编写一个异步通用Picker选择器,用于时间日期、城市、商品分类的选择

    目录 一.功能规划 二.最底层基础实现 (1)Picker界面和功能实现 (2)不同类型的选择器基础实现 三.数据源层 (1)时间日期 (2)多级同步分类,如:城市 (3)多级异步分类,如:城市 四. ...

  5. 爱,死亡和机器人(Love,Death&Robots)

    从我自己的角度来讲,我真的是很喜欢这部短片,奇幻,科幻,喜剧交叉在一起构成了这18部短片.精彩绝伦,我只能这么去形容. 但是有没有不足呢?客观的来说,也存在不足,过度的吹捧使得有些人神话了它,认为立意 ...

  6. php如何定义数组常量

    是这样吗?<?php define('BEST_PHPER',array('name'=>'巩文','address'=>'china')); My God,明确告诉你不可以:原因是 ...

  7. .Net之Layui多图片上传

    前言: 多图上传在一些特殊的需求中我们经常会遇到,其实多图上传的原理大家都有各自的见解.对于Layui多图上传和我之前所说的通过js获取文本框中的文件数组遍历提交的原理一样,只不过是Layui中的up ...

  8. springboot使用RabbitMQ实现延时任务

    延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费.那么,为什么需要延迟消费呢?我们来看以下的场景 订单业务: 在电商/点餐中,都有下单后 30 分钟内没有付款 ...

  9. 常用的方法论-SMART

  10. HTTP 学习笔记01

    HTTP   hypertext transfer protocol (超文本传输协议) TCP/IP 协议集中的一个应用层协议 用于定义WEB浏览器与WEB服务器之间交换数据的过程以及数据本身的格式 ...