.NET Core 3.0之深入源码理解HttpClientFactory(二)
写在前面
上一篇文章讨论了通过在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: }
.NET Core 3.0之深入源码理解HttpClientFactory(二)的更多相关文章
- .NET Core 3.0之深入源码理解Host(二)
写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...
- .NET Core 3.0之深入源码理解Configuration(二)
文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义 ...
- .NET Core 3.0之深入源码理解HttpClientFactory(一)
写在前面 创建HttpClient实例的时候,在内部会创建HttpMessageHandler链,我们知道HttpMessageHandler是负责建立连接的抽象处理程序,所以HttpClient的维 ...
- .NET Core 3.0之深入源码理解Startup的注册及运行
原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...
- .NET Core 3.0之深入源码理解Configuration(一)
Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...
- .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)
写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...
- .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)
前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...
- .NET Core 3.0之深入源码理解ObjectPool(一)
写在前面 对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销. 它使用取用/归还的操作模式,并重复执行这些 ...
- .NET Core 3.0之深入源码理解HealthCheck(一)
写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...
随机推荐
- impala学习笔记
impala学习笔记 -- 建库 CREATE DATABASE IF NOT EXISTS database_name; -- 在HDFS文件系统中创建数据库,需要指定要创建数据库的位置. CREA ...
- 《大型网站系统与Java中间件》读书笔记 (中)
前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 回顾上一篇: <大型网站系统与Java中间件& ...
- 浅入深出Vue:路由
路由的概念在计算机界中的历史大概可以追溯到OSI模型中的数据链路层与网络层中的定义.这里的定义大意是:在转发数据包时,根据数据包的目的地址进行寻址,从而将数据包发往指定的目的地. 在 Web开发中同样 ...
- 2018.10.1 2018NOIP冲刺之立体图
2008NOIP普及组立体图 请自行百度(事实上放不下了) 图不是很清楚 下面有 [输入] 输入文件 drawing.in 第一行有用空格隔开的 2 个整数 m 和 n,表示有 m*n 个格子 (1 ...
- Git使用小技巧之多个远程仓库
想要获取更多文章可以访问我的博客 - 代码无止境. 这是一个普通的工作日,小代正在勤勤恳恳的写代码.这时陈BOSS走到小代身边,跟小代说:"我们的代码需要同时推送到Github和码云两个仓库 ...
- 用链表和数组实现HASH表,几种碰撞冲突解决方法
Hash算法中要解决一个碰撞冲突的办法,后文中描述了几种解决方法.下面代码中用的是链式地址法,就是用链表和数组实现HASH表. he/*hash table max size*/ #define HA ...
- VS2013日常使用若干技巧+快捷键
1.注释的方法 1)sqlserver中,单行注释:— — 多行注释:/* 代码 */ 2)C#中,单行注释:// 多行注释:/* 代码 */ 3)C#中多行注释的快捷方式:先选中你要注 ...
- Linux 安装 lanmp
Lanmp介绍 lanmp一键安装包是wdlinux官网2010年底开始推出的web应用环境的快速简易安装包. 执行一个脚本,整个环境就安装完成就可使用,快速,方便易用,安全稳定 lanmp一键安装包 ...
- nexus私服快速update index方法
简单搭好nexus私服后,做好基本的配置. 稍微麻烦点的就是让其更新Jar索引文件.如果有耐心的话,完全可以通过在线更新索引的方式来做,但所消耗的时间较长,让人误以为出错了,不知所措. 下面介绍一种简 ...
- Mac上Ultra Edit的激活
2016-11-20 增加16.10.0.22破解 去官网下载原载,先运行一次,再在终端里执行下面代码就可以破解完成!printf '\x31\xC0\xFF\xC0\xC3\x90' | dd se ...