写在前面

上一篇文章讨论了通过在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(二)的更多相关文章

  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. java中的String、StringBuffer、StringBuilder的区别

    java中String.StringBuffer.StringBuilder是编程中经常使用的字符串类,他们之间的区别也是经常在面试中会问到的问题.现在总结一下,看看他们的不同与相同. 1.可变与不可 ...

  2. 从电子游戏到DevOps

    在一个项目团队中,开发与运维之间的关系像极了知名大型游戏<刺客信条>里的故事:开发就是追求自由的刺客联盟——我喜欢用各种新颖技术手段去满足用户爸爸那些花里胡哨的需求,你别管那技术好不好用, ...

  3. 进程以及进程通信(IPC)类型

    这里用我有限的知识来解释同时参考了一些其他博主的子类,希望能给与一部分入门的朋友一个清晰的理解,有问题之处还请指出 首先简单谈一下什么是进程? 答:进程是装入内存运行的程序段,是许多的系统对象拥有权的 ...

  4. 第三章: Expressions and Flow Control

    第三章: Expressions and Flow Control一:局部变量和实例变量定义变量是指设定变量的数据类型和变量的名字,Java语言要求变量遵循先定义,再初始化,然后使用的规则.作用域:指 ...

  5. Appcan 日期控件

    某个页面的onclick事件 <div id="topSendDate" class="ub ub-f1" onclick="appcan.wi ...

  6. 长春理工大学第十四届程序设计竞赛(重现赛)J

    J.Printout 题目:链接:https://ac.nowcoder.com/acm/contest/912/J 题目: 小r为了打校赛,他打算去打字社打印一份包含世界上所有算法的模板. 到了打字 ...

  7. HDU 3338:Kakuro Extension(脑洞大开的网络流)

    http://acm.hdu.edu.cn/showproblem.php?pid=3338 题意:在一个n*m的地图里面,有黑方块和白方块,黑方块可能是“XXXXXXX”或者“YYY/YYY”,这里 ...

  8. 在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

    DevExpress提供的树形列表控件TreeList和树形下拉列表控件TreeListLookupEdit都是非常强大的一个控件,它和我们传统Winform的TreeView控件使用上有所不同,我一 ...

  9. c# bool类型和int类型的互转

    项目过程中,会有model的一些属性字段为‘是’或‘否’ 数据库字段一半定义为int,值则是0或1 数据库model转实体类的时候,bool和int可以直接相互转换 false强转int 值就是0 t ...

  10. c++学习书籍推荐《C++编程思想第二卷》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <C++编程思想>(第2卷)是惟一一本如此清晰地阐述如何重新思考以面向对象方法构造程序的书籍.<C++编程思想>(第2卷)介绍实用的编 ...