ASP.NET Core 在 2.1 以后推出了具备弹性 HTTP 请求能力的 HttpClient 工厂类 HttpClientFactory。git
替换的初衷仍是简单摆一下:
① using(var client = new HttpClient()) 调用的 Dispose() 方法并不会当即释放底层 Socket 链接,新建 Socket 须要时间,致使在高并发场景下 Socket 耗尽。
② 基于 ① 不少人会想到使用单例或者静态类构造 HttpClient 实例,可是这里有一个坑,HttpClient 不会反应 DNS 的变动。github
HttpClientFactory 以模块化、可命名、可配置、弹性方式重建了 HttpClient 的使用方式: 由 DI 框架注入 IHttpClientFactory 工厂;由工厂建立 HttpClient 并从内部的 Handler 池分配请求 Handler。算法
HttpClient 可在 DI 框架中经过
IHttpCLientBuilder
对象配置 Policy 策略。设计模式
我一直对这种颠覆传统 HttpClient 的代码组织方式感到好奇,今天咱们带着问题来探究一下新版 HttpClient 的实现。数组
一个完整的 HttpClient 包括三部分:服务器
附加的 HttpMessageHandler 须要与核心 HttpMessageHandler 造成链式 Pipeline 关系,最终端点指向核心 HttpMessageHandler,
链表数据结构是 DelegatingHandler 关键类(包含 InnerHandler 链表节点指针)数据结构
很明显,HttpClientFactory 源码的解读分为 2 部分,内心藏着伪代码,带着问题思考更香(手动狗头)。并发
在 Startup.cs 文件开始配置要用到的 HttpClientapp
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(12)) .AddPolicyHandler(GetRetryPolicy(3));
配置过程充分体现了.NET Core 推崇的万物皆服务,配置前移
的 DI 风格;
同对时 HttpClient 的基础、配置均经过配置即委托
来完成框架
Q1. 如何记录以上配置?
微软使用一个HttpClientFactoryOptions
对象来记录 HttpClient 配置,这个套路是否是很熟悉?
AddHttpClient
扩展方法产生 HttpClientBuilder 对象ConfigurePrimaryHttpMessageHandler
扩展方法会将核心 Handler 插到 Options 对象的 HttpMessageHandlerBuilderActions 数组,做为 Handlers 数组中的 PrimaryHandlerAddPolicyHandler
扩展方法也会将 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。
经过 IHttpClientFactory.CreateClient() 产生的 HttpClient 实例有一些内部行为:
标准的 HttpClient(不带 Policy 策略)除了 PrimaryHandler 以外,微软给你附加了两个 AdditionHandler:
以后将排序后的 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(0, 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 - 1; i >= 0; 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 阶段注入的几个服务:
咱们探究System.Net.Http库的目的:
学习精良的设计模式、理解默认的DI行为;
默认DI行为给咱们提供了扩展/改造 HttpClientFactory 的一个思路。
附赠有关应用 HttpClient 的三个瓜:
1. AllowAutoRedirect 属性 : 控制请求收到重定向响应 能 发起跳转请求, 默认为 true
2. AutomaticDecompression枚举: 支持对服务端response 压缩的解码
3. ServerCertificateCustomValidationCallback: 自定义对服务器证书的认证逻辑