zoukankan      html  css  js  c++  java
  • 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(12))
                   .AddPolicyHandler(GetRetryPolicy(3));

    配置过程充分体现了.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 来控制retryCircuit 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 阶段注入的几个服务:

    • 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: 自定义对服务器证书的认证逻辑

  • 相关阅读:
    php 计算两点地理坐标的距离
    objective-c中#import和@class的差别
    我与小娜(07):量子通信的奥
    OpenStack部署到Hadoop的四种方案
    iOS UI13_数据解析XML_,JSON
    Redis集群
    《AndroidStudio有用指南》反馈问题和建议
    numpy函数库中一些经常使用函数的记录
    1015 history grep |mysql
    php 生成压缩文件
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/12405973.html
Copyright © 2011-2022 走看看