一、前言

前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandlerPrimaryHttpMessageHandler 的使用场景和区别

二、源代码阅读

2.1 核心消息管道模型图

先贴上一张核心MessageHandler 管道模型的流程图,图如下:HttpClient 中的HttpMessageHandler 负责主要核心的业务,HttpMessageHandler 是由MessageHandler 链表结构组成,形成一个消息管道模式;具体我们一起来看看源代码

2.2 Demo代码演示

再阅读源代码的时候我们先来看下下面注入HttpClient 的Demo 代码,代码如下:

services.AddHttpClient("test").ConfigurePrimaryHttpMessageHandler(provider =>{return new PrimaryHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new LogHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new Log2HttpMessageHandler(provider);});

上面代码中有两个核心扩展方法,分别是ConfigurePrimaryHttpMessageHandlerAddHttpMessageHandler,这两个方法大家可能会有疑问是做什么的呢?不错,这两个方法就是扩展注册自定义的HttpMessageHandler 如果不注册,会有默认的HttpMessageHandler,接下来我们分别来看下提供的扩展方法,如下图:图中提供了一系列的AddHttpMessageHandler 扩展方法和ConfigurePrimaryHttpMessageHandler的扩展方法。

2.3 AddHttpMessageHandler

我们来看看HttpClientBuilderExtensions中的其中一个AddHttpMessageHandler扩展方法,代码如下:

        /// <summary>/// Adds a delegate that will be used to create an additional message handler for a named <see cref="HttpClient"/>./// </summary>/// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>/// <param name="configureHandler">A delegate that is used to create a <see cref="DelegatingHandler"/>.</param>/// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>/// <remarks>/// The <see paramref="configureHandler"/> delegate should return a new instance of the message handler each time it/// is invoked./// </remarks>public static IHttpClientBuilder AddHttpMessageHandler(this IHttpClientBuilder builder, Func<DelegatingHandler> configureHandler){if (builder == null){throw new ArgumentNullException(nameof(builder));}if (configureHandler == null){throw new ArgumentNullException(nameof(configureHandler));}builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>{options.HttpMessageHandlerBuilderActions.Add(b => b.AdditionalHandlers.Add(configureHandler()));});return builder;}

代码中把自定义的DelegatingHandler 方法添加到HttpMessageHandlerBuilderActions中,我们再来看看HttpClientFactoryOptions对象源代码,如下:

 /// <summary>/// An options class for configuring the default <see cref="IHttpClientFactory"/>./// </summary>public class HttpClientFactoryOptions{// Establishing a minimum lifetime helps us avoid some possible destructive cases.//// IMPORTANT: This is used in a resource string. Update the resource if this changes.internal readonly static TimeSpan MinimumHandlerLifetime = TimeSpan.FromSeconds(1);private TimeSpan _handlerLifetime = TimeSpan.FromMinutes(2);/// <summary>/// Gets a list of operations used to configure an <see cref="HttpMessageHandlerBuilder"/>./// </summary>public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();/// <summary>/// Gets a list of operations used to configure an <see cref="HttpClient"/>./// </summary>public IList<Action<HttpClient>> HttpClientActions { get; } = new List<Action<HttpClient>>();/// <summary>/// Gets or sets the length of time that a <see cref="HttpMessageHandler"/> instance can be reused. Each named/// client can have its own configured handler lifetime value. The default value of this property is two minutes./// Set the lifetime to <see cref="Timeout.InfiniteTimeSpan"/> to disable handler expiry./// </summary>/// <remarks>/// <para>/// The default implementation of <see cref="IHttpClientFactory"/> will pool the <see cref="HttpMessageHandler"/>/// instances created by the factory to reduce resource consumption. This setting configures the amount of time/// a handler can be pooled before it is scheduled for removal from the pool and disposal./// </para>/// <para>/// Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating/// more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely/// which can prevent the handler from reacting to DNS changes. The value of <see cref="HandlerLifetime"/> should be/// chosen with an understanding of the application's requirement to respond to changes in the network environment./// </para>/// <para>/// Expiry of a handler will not immediately dispose the handler. An expired handler is placed in a separate pool/// which is processed at intervals to dispose handlers only when they become unreachable. Using long-lived/// <see cref="HttpClient"/> instances will prevent the underlying <see cref="HttpMessageHandler"/> from being/// disposed until all references are garbage-collected./// </para>/// </remarks>public TimeSpan HandlerLifetime{get => _handlerLifetime;set{if (value != Timeout.InfiniteTimeSpan && value < MinimumHandlerLifetime){throw new ArgumentException(Resources.HandlerLifetime_InvalidValue, nameof(value));}_handlerLifetime = value;}}/// <summary>/// The <see cref="Func{T, R}"/> which determines whether to redact the HTTP header value before logging./// </summary>public Func<string, bool> ShouldRedactHeaderValue { get; set; } = (header) => false;/// <summary>/// <para>/// Gets or sets a value that determines whether the <see cref="IHttpClientFactory"/> will/// create a dependency injection scope when building an <see cref="HttpMessageHandler"/>./// If <c>false</c> (default), a scope will be created, otherwise a scope will not be created./// </para>/// <para>/// This option is provided for compatibility with existing applications. It is recommended/// to use the default setting for new applications./// </para>/// </summary>/// <remarks>/// <para>/// The <see cref="IHttpClientFactory"/> will (by default) create a dependency injection scope/// each time it creates an <see cref="HttpMessageHandler"/>. The created scope has the same/// lifetime as the message handler, and will be disposed when the message handler is disposed./// </para>/// <para>/// When operations that are part of <see cref="HttpMessageHandlerBuilderActions"/> are executed/// they will be provided with the scoped <see cref="IServiceProvider"/> via/// <see cref="HttpMessageHandlerBuilder.Services"/>. This includes retrieving a message handler/// from dependency injection, such as one registered using/// <see cref="HttpClientBuilderExtensions.AddHttpMessageHandler{THandler}(IHttpClientBuilder)"/>./// </para>/// </remarks>public bool SuppressHandlerScope { get; set; }}

源代码中有如下核心List:

 public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();

提供了HttpMessageHandlerBuilder HttpMessageHandler 的构造器列表对象,故,通过AddHttpMessageHandler可以添加一系列的消息构造器方法对象 我们再来看看这个消息构造器类,核心部分,代码如下:

public abstract class HttpMessageHandlerBuilder{/// <summary>/// Gets or sets the name of the <see cref="HttpClient"/> being created./// </summary>/// <remarks>/// The <see cref="Name"/> is set by the <see cref="IHttpClientFactory"/> infrastructure/// and is public for unit testing purposes only. Setting the <see cref="Name"/> outside of/// testing scenarios may have unpredictable results./// </remarks>public abstract string Name { get; set; }/// <summary>/// Gets or sets the primary <see cref="HttpMessageHandler"/>./// </summary>public abstract HttpMessageHandler PrimaryHandler { get; set; }/// <summary>/// Gets a list of additional <see cref="DelegatingHandler"/> instances used to configure an/// <see cref="HttpClient"/> pipeline./// </summary>public abstract IList<DelegatingHandler> AdditionalHandlers { get; }/// <summary>/// Gets an <see cref="IServiceProvider"/> which can be used to resolve services/// from the dependency injection container./// </summary>/// <remarks>/// This property is sensitive to the value of/// <see cref="HttpClientFactoryOptions.SuppressHandlerScope"/>. If <c>true</c> this/// property will be a reference to the application's root service provider. If <c>false</c>/// (default) this will be a reference to a scoped service provider that has the same/// lifetime as the handler being created./// </remarks>public virtual IServiceProvider Services { get; }/// <summary>/// Creates an <see cref="HttpMessageHandler"/>./// </summary>/// <returns>/// An <see cref="HttpMessageHandler"/> built from the <see cref="PrimaryHandler"/> and/// <see cref="AdditionalHandlers"/>./// </returns>public abstract HttpMessageHandler Build();protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable<DelegatingHandler> additionalHandlers){// This is similar to https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Net.Http.Formatting/HttpClientFactory.cs#L58// but we don't want to take that package as a dependency.if (primaryHandler == null){throw new ArgumentNullException(nameof(primaryHandler));}if (additionalHandlers == null){throw new ArgumentNullException(nameof(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);}// Checking for this allows us to catch cases where someone has tried to re-use a handler. That really won't// work the way you want and it can be tricky for callers to figure out.if (handler.InnerHandler != null){var message = Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid(nameof(DelegatingHandler.InnerHandler),nameof(DelegatingHandler),nameof(HttpMessageHandlerBuilder),Environment.NewLine,handler);throw new InvalidOperationException(message);}handler.InnerHandler = next;next = handler;}return next;}}

HttpMessageHandlerBuilder构造器中有两个核心属性PrimaryHandlerAdditionalHandlers ,细心的同学可以发现AdditionalHandlers是一个IList<DelegatingHandler>列表,也就是说可以HttpClient 可以添加多个DelegatingHandler 即多个HttpMessageHandler 消息处理Handler 但是只能有一个PrimaryHandler Handler

同时HttpMessageHandlerBuilder提供了一个抽象的Build方法,还有一个CreateHandlerPipeline 方法,这个方法主要是把IList<DelegatingHandler>PrimaryHandler 构造成一个MessageHandler 链表结构(通过DelegatingHandlerInnerHandler属性进行连接起来)

2.4 ConfigurePrimaryHttpMessageHandler

 public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Func<HttpMessageHandler> configureHandler){if (builder == null){throw new ArgumentNullException(nameof(builder));}if (configureHandler == null){throw new ArgumentNullException(nameof(configureHandler));}builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>{options.HttpMessageHandlerBuilderActions.Add(b => b.PrimaryHandler = configureHandler());});return builder;}

通过上面的HttpMessageHandlerBuilder 源代码分析ConfigurePrimaryHttpMessageHandler 方法主要是给Builder 中添加PrimaryHandler消息Handler

2.5 DefaultHttpMessageHandlerBuilder

我们知道在services.AddHttpClient() 方法中会注册默认的DefaultHttpMessageHandlerBuilder 消息构造器方法,它继承DefaultHttpMessageHandlerBuilder,那我们来看看它的源代码

internal class DefaultHttpMessageHandlerBuilder : HttpMessageHandlerBuilder{public DefaultHttpMessageHandlerBuilder(IServiceProvider services){Services = services;}private string _name;public override string Name{get => _name;set{if (value == null){throw new ArgumentNullException(nameof(value));}_name = value;}}public override HttpMessageHandler PrimaryHandler { get; set; } = new HttpClientHandler();public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>();public override IServiceProvider Services { get; }public override HttpMessageHandler Build(){if (PrimaryHandler == null){var message = Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler));throw new InvalidOperationException(message);}return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers);}

代码中Build 会去调用HttpMessageHandlerBuilder 的CreateHandlerPipeline方法把HttpMessageHandler 构建成一个类似于链表的结构。到这里源代码已经分析完了,接下来我们来演示一个Demo,来证明上面的核心HttpMessageHandler 流程走向图

三、Demo演示证明

我们继续来看上面我的Demo代码:

services.AddHttpClient("test").ConfigurePrimaryHttpMessageHandler(provider =>{return new PrimaryHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new LogHttpMessageHandler(provider);}).AddHttpMessageHandler(provider =>{return new Log2HttpMessageHandler(provider);});

代码中自定义了两个HttpMessageHandler和一个PrimaryHttpMessageHandler我们再来分别看看Log2HttpMessageHandlerLogHttpMessageHandlerPrimaryHttpMessageHandler 代码,代码很简单就是SendAsync前后输出了Log信息,代码如下:自定义的PrimaryHttpMessageHandler 代码如下:

public class PrimaryHttpMessageHandler: DelegatingHandler{private IServiceProvider _provider;public PrimaryHttpMessageHandler(IServiceProvider provider){_provider = provider;InnerHandler = new HttpClientHandler();}protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){System.Console.WriteLine("PrimaryHttpMessageHandler Start Log");var response= await base.SendAsync(request, cancellationToken);System.Console.WriteLine("PrimaryHttpMessageHandler End Log");return response;}}

Log2HttpMessageHandler 代码如下:

 public class Log2HttpMessageHandler : DelegatingHandler{private IServiceProvider _provider;public Log2HttpMessageHandler(IServiceProvider provider){_provider = provider;//InnerHandler = new HttpClientHandler();}protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){System.Console.WriteLine("LogHttpMessageHandler2 Start Log");var response=await base.SendAsync(request, cancellationToken);System.Console.WriteLine("LogHttpMessageHandler2 End Log");return response;}}

LogHttpMessageHandler代码如下:

 public class LogHttpMessageHandler : DelegatingHandler{private IServiceProvider _provider;public LogHttpMessageHandler(IServiceProvider provider){_provider = provider;//InnerHandler = new HttpClientHandler();}protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){System.Console.WriteLine("LogHttpMessageHandler Start Log");var response=await base.SendAsync(request, cancellationToken);System.Console.WriteLine("LogHttpMessageHandler End Log");return response;}}

三个自定义Handler 代码已经完成,我们继续添加调用代码,如下:

        /// <summary>////// </summary>/// <param name="url"></param>/// <returns></returns>public async Task<string> GetBaiduAsync(string url){var client = _clientFactory.CreateClient("test");var result = await client.GetStringAsync(url);return result;}

现在我们运行访问接口,运行后的控制台Log 如下图:看到输出结果,大家有没有发现跟Asp.net core 中的中间件管道的运行图一样。

四、总结

HttpClientHttpMessageHandler可以自定义多个,但是只能有一个PrimaryHttpMessageHandler如果添加多个只会被最后面添加的给覆盖;添加的一系列Handler 构成一个链式管道模型,并且PrimaryHttpMessageHandler 主的消息Handler 是在管道的最外层,也就是管道模型中的最后一道Handler。使用场景:我们可以通过自定义的MessageHandler 来动态加载请求证书,通过数据库的一些信息,在自定义的Handler 中加载注入对应的证书,这样可以起到动态加载支付证书作用,同时可以SendAsync 之前或者之后做一些自己的验证等相关业务,大家只需要理解它们的用途,自然知道它的强大作用,今天就分享到这里。

往期精彩回顾

  • 【.net core】电商平台升级之微服务架构应用实战

  • .Net Core微服务架构技术栈的那些事

  • Asp.Net Core 中IdentityServer4 授权中心之应用实战

  • Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式

  • Asp.Net Core 中IdentityServer4 授权流程及刷新Token

  • Asp.Net Core 中IdentityServer4 实战之 Claim详解

  • Asp.Net Core 中IdentityServer4 实战之角色授权详解

  • Asp.Net Core 中间件应用实战中你不知道的那些事

  • Asp.Net Core Filter 深入浅出的那些事-AOP

  • Asp.Net Core EndPoint 终结点路由工作原理解读

  • ASP.NET CORE 内置的IOC解读及使用

♥ 给个[在看],是对我最大的支持 ♥

.net core HttpClient 使用之消息管道解析(二)相关推荐

  1. .net core HttpClient 使用之掉坑解析(一)

    一.前言 在我们开发当中经常需要向特定URL地址发送Http请求操作,在.net core 中对httpClient使用不当会造成灾难性的问题,这篇文章主要来分享.net core中通过IHttpCl ...

  2. ASP.NET Core 1.0中的管道-中间件模式

    ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middleware)的方式注册在管道中.显而易见这样的设计非常松耦合 ...

  3. 消息队列之延时消息应用解析及实践

    简介:消息队列常用于实现业务需要的异步.解耦以及削峰功能.但在某些特殊的业务场景中,还需要消息队列服务本身支持一些特殊的消息类型,比如常见的延时消息.本次直播为您深入剖析延时消息的特性.应用场景,对比 ...

  4. .Net Core HttpClient处理响应压缩

    前言 在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采用哪种方 ...

  5. 使用gRPC的.NET Core 3.0双向消息流

    目录 介绍 软件描述 服务器基础结构(GrpcServerHelper) 客户基础设施(GrpcClientHelper) GrpcServer和GrpcClient 运行示例 结论 下载源码 - 2 ...

  6. ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事

    ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事 原文:ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事 本文通过一张GIF动图来继续聊一下ASP.NET Core ...

  7. .net core httpclient An error occurred while sending the request

    .net core HttpClient 报错 An error occurred while sending the request ,The response ended prematurely. ...

  8. .net core 3.1JWT用户权限认证(二)获取token

    .net core 3.1JWT用户权限认证(二)获取token 直接上案列 引入程序集 第二步 建立token接口,和token实现类 public interface IJwtTokenServi ...

  9. Java生成和解析二维码

    前言:曾经有做过不少微信公众号和移动网站的项目,对二维码还算有点了解,刚收到这个任务的时候就想着竟然要用二维码存文本,那就得先考究一下这小小的二维码到底能存多少的东西了. 需求:使用二维码存放文本(x ...

最新文章

  1. 探究位置误差对单目3D目标检测影响
  2. ORB-SLAM(八)ORBmatcher 特征匹配
  3. python画圆简单代码-python实现画圆功能
  4. IntelliJ IDEA的几种常见的快捷键
  5. cisco SMD 配置安装
  6. 标准常有,VVC不常有
  7. 深入理解python的元组本身不可变性
  8. oracle中or会使索引,为何查询条件中多了 OR 选项,索引就走全扫描?
  9. 小米第二款5G手机是小米9?升级版小米9 配置强悍!
  10. OpenCV精进之路(十四):图像矫正技术深入探讨
  11. 51CTO网友感谢信:昨天以940分的成绩顺利通过NE考试 在此特地感谢网工泡泡
  12. XenCenter开启虚机提示VDI不可用
  13. CS231n:作业1——KNN
  14. garbor 特征 matlab,Gabor小波滤波用于纹理特征提取
  15. ios上架应用在苹果商店搜不到
  16. python编写的动物识别专家系统_基于Python的动物识别专家系统
  17. input自动填充后背景颜色被覆盖的问题
  18. 【阿朱洞察】中国大数据行业的下一步走向
  19. 亲身试验力荐:番茄工作法
  20. 解决Google浏览器卸载后无法重新安装的问题

热门文章

  1. [Java] java中的接口定义
  2. .NET平台下几种SOCKET模型的简要性能供参考
  3. SQL中合并多行记录的方法总汇
  4. [方法“Boolean Contains(System.Guid)”不支持转换为 SQL]的解决办法
  5. 如何在Outlook中的电子邮件上显示快速操作按钮
  6. dock怎么自定义_如何自定义和调整Mac的Dock
  7. 共轨之家获吉利家族基金新一轮融资 5个月前曾获磐霖资本领投A轮融资
  8. Java之品优购课程讲义_day20(5)
  9. LinkedHashMap的实现原理
  10. 海尔联手软银机器人,进军服务机器人领域