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

一、前言

前面分享了 .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解读及使用


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309669.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[汇编语言]实验五:编写,调试具有多个段的程序

&#xff08;1&#xff09; 实验代码: assume cs:code, ds:data,ss:stackdata segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data endsstack segmentdw 0,0,0,0,0,0,0,0 stack endscode segmentstart: mov ax,stackmov ss,axmov sp,16mov ax,datamov ds,axpus…

Fibonacci Sum HDU - 6755【2020 Multi-University Training Contest 1】斐波那契数列变形+二项式定理

【杭电多校2020】Distinct Sub-palindromes 分析&#xff1a; 题目&#xff1a; The Fibonacci numbers are defined as below: Given three integers N, C and K, calculate the following summation: Since the answer can be huge, output it modulo 1000000009 (1091…

List的扩容机制,你真的明白吗?

一&#xff1a;背景1. 讲故事在前一篇大内存排查中&#xff0c;我们看到了Dictionary正在做扩容操作&#xff0c;当时这个字典的count251w&#xff0c;你把字典玩的66飞起&#xff0c;其实都是底层为你负重前行&#xff0c;比如其中的扩容机制&#xff0c;当你遇到几百万甚至千…

[汇编语言]用[bx+idata]的方式进行数组的处理-字母大小写转换

第一个字符串"BaSiC"中的小写字母变成大写字母&#xff1b; 第二个字符串"iNfOrMaTiOn"中的大写字母变成小写字母&#xff1b; 方法一: 代码如下: assume cs:codesg,ds:datasgdatasg segment db BaSiC db iNfOrMaTiOn datasg endscodesg segment start…

中科大软件测试期末复习

前言 taozs老师画的重点&#xff0c;极其重要&#xff01;&#xff01;&#xff01; 25道多选 测试是为了证明这个系统没有bug。 错 测试四象限&#xff1a; 单元测试&#xff08;工具&#xff09;、组件测试&#xff08;开发人员做&#xff0c;dao层 controller层&#xf…

.NET 程序下锐浪报表 (Grid++ Report) 的绿色发布指南

在锐浪报表官方为 CSharp 编写的开发文档&#xff1a;“在C#与VB.NET中开始使用说明.txt” 中&#xff0c;关于发布项目是这么描述的&#xff1a;★发布你的项目&#xff0c;用VS.NET制作安装程序&#xff1a;1、先创建安装项目&#xff1a;在解决方案资源管理器的根节点上点右…

C++输出对齐(如何使输出对齐)

代码如下: #include <iostream> #include <iomanip> using namespace std;int main() {cout << setw(15)<<std::left<<"unsigned int" << setw(15) << std::left<<sizeof(unsigned) << endl;cout <<…

HDFS(一)

HDFS&#xff08;一&#xff09; 参考&#xff1a; http://hadoop.apache.org/docs/r1.0.4/cn/hdfs_design.html https://www.cnblogs.com/zsql/p/11587240.html Hadoop Distribute File System&#xff1a;Hadoop分布式文件系统&#xff0c;Hadoop核心组件之一&#xff0c;作为…

[开源] .Net orm FreeSql 1.5.0 最新版本(番号:好久不见)

废话开头这篇文章是我有史以来编辑最长时间的&#xff0c;历时 4小时&#xff01;&#xff01;&#xff01;原本我可以利用这 4小时编写一堆胶水代码&#xff0c;真心希望善良的您点个赞&#xff0c;谢谢了&#xff01;&#xff01;很久很久没有写文章了&#xff0c;上一次还是…

MapReduce简述

MapReduce 参考&#xff1a; https://www.cnblogs.com/lixiansheng/p/8942370.html https://baike.baidu.com/item/MapReduce/133425?fraladdin 概念 MapReduce是面向大数据并行处理的计算模型&#xff0c;用于大规模数据集的并行计算。它提供了一个庞大但设计精良的并行计算…

调试实战 —— dll 加载失败之全局变量初始化篇

前言 最近项目里总是遇到 dll 加载不上的问题&#xff0c;原因各种各样。今天先总结一个虽然不是项目中实际遇到的问题&#xff0c;但是却非常经典的问题。其它几种问题&#xff0c;后续慢慢总结。示例代码包含一个 exe 工程&#xff0c;两个 dll 工程。exe 会加载两个 dll 并调…

MongoDB副本集

参考&#xff1a;https://www.cnblogs.com/littleatp/p/8562842.html https://www.cnblogs.com/ilifeilong/p/14347008.html MongoDB副本集 MongoDB副本集是由一组Mongod实例&#xff08;进程&#xff09;组成&#xff0c;包含一个Primary节点和多个Secondary节点。客户端的所…

博客系统知多少:揭秘那些不为人知的学问(一)

点击上方蓝字关注“汪宇杰博客”导语在我们生活的年代&#xff0c;博客并不稀奇&#xff0c;甚至可以说是随处可见。从最早的搜狐、新浪博客&#xff0c;再到每个人都曾记录青春的 QQ 空间&#xff0c;再到现在的 Vlog 与 Plog&#xff0c;似乎拥有一个自己的博客并不是什么难事…

MongoDB 分片

MongoDB 分片 高数据量&#xff08;消耗内存&#xff09;和高吞吐量&#xff08;消耗CPU&#xff09;的数据库应用会对单机的性能造成较大压力&#xff0c;为了解决这些问题&#xff0c;一般采用两种方法&#xff1a;水平扩展&#xff08;将数据集分布在多个服务器上&#xff…

How many ways HDU - 1978(记忆化搜索关于求多少种方式模板)

题目&#xff1a; 这是一个简单的生存游戏&#xff0c;你控制一个机器人从一个棋盘的起始点(1,1)走到棋盘的终点(n,m)。游戏的规则描述如下&#xff1a; 1.机器人一开始在棋盘的起始点并有起始点所标有的能量。 2.机器人只能向右或者向下走&#xff0c;并且每走一步消耗一单位…

Sql Server之旅——第七站 复合索引和include索引到底有多大区别?

索引和锁&#xff0c;这两个主题对我们开发工程师来说&#xff0c;非常的重要。。。只有理解了这两个主题&#xff0c;我们才能写出高质量的sql语句&#xff0c;在之前的博客中&#xff0c;我所说的索引都是单列索引。。。当然数据库不可能只认单列索引&#xff0c;还有我这篇的…