HttpClientFactory的套路,你知多少?
背景
ASP.NET Core 在 2.1 之后推出了具有弹性 HTTP 请求能力的 HttpClient 工厂类 HttpClientFactory。
替换的初衷还是简单摆一下:
① using(var client = new HttpClient()) 调用的 Dispose() 方法并不会立即释放底层 Socket 连接,新建 Socket 需要时间,导致在高并发场景下 Socket 耗尽。
② 基于 ① 很多人会想到使用单例或者静态类构造 HttpClient 实例,但是这里有一个坑,HttpClient 不会反应 DNS 的变更。
HttpClientFactory 以模块化、可命名、可配置、弹性方式重建了 HttpClient 的使用方式:
由 DI 框架注入 IHttpClientFactory 工厂;由工厂创建 HttpClient 并从内部的 Handler 池分配请求 Handler。
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())
.AddPolicyHandler(GetRetryPolicy());
配置过程充分体现了.NET Core 推崇的万物皆服务,配置前移
的 DI 风格;
同对时 HttpClient 的基础、配置均通过配置即委托
来完成
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 来控制retry
、Circuit 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(, 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 - ; i >= ; 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: 自定义对服务器证书的认证逻辑
HttpClientFactory的套路,你知多少?的更多相关文章
- 菜鸟浅谈“诈骗”希望“治未病"
关于目前诈骗.社工数据的套路,说道说道~ 一.前言 这篇文章没有什么高深的技术,只有普普通通的套路,主要也是有I春秋各位表哥与诈骗分子的交手有感而发! 二.正文 因为我们上网的或者其他条件下的人群,没 ...
- 最牛B的编码套路 - 呦呦鹿鸣 - 博客频道 - CSDN.NET
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- 套路!从Ruby 到 Cocoapods的发布
前言: 现在的社会讲究的是套路,作为一名iOS工程师, 一言不合我要发套路了! 一.Ruby(ruby环境已经安装了的朋友可以跳过这一点) 准备: Mac OSX 安装xcode,它会帮你安装好 Un ...
- 一道在知乎很火的 Java 题——如何输出 ab【转】
这是一个源自知乎的话题,原贴链接:一道百度的面试题,有大神会嘛? 虽然我不是大神,但我也点进去看了一下,思考了一会之后有了一些思路,然后去看其它人的答案的时候果然全都已经被各路大神们先想到并贴出来了, ...
- python网络爬虫,知识储备,简单爬虫的必知必会,【核心】
知识储备,简单爬虫的必知必会,[核心] 一.实验说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou 2. 环境介绍 本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌 ...
- crypto必知必会
crypto必知必会 最近参加了个ctf比赛,在i春秋,南邮方面刷了一些crypto密码学题目,从中也增长了不少知识,在此关于常见的密码学知识做个小总结! Base编码 Base编码中用的比较多的是b ...
- Android 利用an框架快速实现夜间模式的两种套路
作者:Bgwan链接:https://zhuanlan.zhihu.com/p/22520818来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 网上看到过大多实现夜间模 ...
- (转)面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题
背景:redis问题在面试过程中经常被问到,对于常见问题一定不能放过. 面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题 一.缓存雪崩 1.1什么是缓存雪崩? 如果缓存数据设置的过 ...
- ASP.NET Core 2.1 中的 HttpClientFactory (Part 2) 定义命名化和类型化的客户端
原文:https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore 发表于:2018年1月 上一篇文 ...
随机推荐
- Halcon系列(1) 菜鸟入门
官方网站怎么使用HDevelop :https://www.mvtec.com/products/halcon/halcon-tour
- 达梦、oracel、mysql数据库兼容
联合表更新sql语句: 只支持mysql.oracle,不支持达梦 update to_pub_report a, to_pub_rec_process b set a.Satisfy_ID , a. ...
- JAVA专业术语面试100问
前言:面试技巧另外开篇再说,先上面试干货吧.Redis.消息队列.SQL不要走开,关注后更精彩! 1.面向对象的特点有哪些? 抽象.继承.封装.多态. 2.接口和抽象类有什么联系和区别? 3.重载和重 ...
- Java设计模-过滤器模式
过滤器模式 过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接 ...
- spring boot原理分析启动依赖中parent帮我们干了什么
主要内容: 1:分析spring-boot-starter-parent 这个依赖 通过前面几篇文章的学习,我们感受到了spring boot的魅力.最明显的感觉就是pom.xml文件.代码少了很多. ...
- 用Spring Tool Suite简化你的开发
如果你是一个喜欢用spring的人,你可能会在欣赏spring的强大功能外,对其各样的配置比较郁闷,尤其是相差较大的版本在配置文件方面会存在差异,当然你可以去花不少的时间去网上查找相关的资料,当你准备 ...
- 转载-WebSocket协议解析
现在,很多网站为了实现推送技术,所用的技术都是轮询.轮询是指在特定的时间间隔(如每一秒),由浏览器对服务器发起HTTP请求,然后由服务器返回数据给浏览器.由于HTTP协议是惰性的,只有客户端发起请求, ...
- 使用httpclient必须知道的参数设置及代码写法、存在的风险
转发地址:http://jinnianshilongnian.iteye.com/blog/2089792 结论: 如果使用httpclient 3.1并发量比较大的项目,最好升级到httpclien ...
- PHP--foreach的问题
<?php echo "<pre>"; $data = ['a', 'b', 'c']; foreach($data as $key => $val){ $ ...
- RDS的tar文件恢复到本地mysql5.7版本数据库
参考博客: 安装qpress软件 https://blog.csdn.net/a18838964650/article/details/82800621 文章介绍补充 https://www.cnbl ...