一、前言

前面分享了 .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 HttpClient 使用之消息管道解析(二)的更多相关文章

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

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

  2. Web API框架学习——消息管道(二)

    HttpServer的GlobalConfiguration中创建: GlobalConfiguration中确定了第一个HttpMessageHandler消息管道: 首:DefaultServer ...

  3. Core 1.0中的管道-中间件模式

    ASP.NET Core 1.0中的管道-中间件模式 SP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middlewar ...

  4. .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类

    .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类 0x00 为什么要引入扩展方法 有的中间件功能比较简单,有的则比较复杂,并且依赖其它组件.除 ...

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

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

  6. .net Core 调用微信Jsapi接口,H5解析二维码

    项目里需要用到扫描二维码,自己实现,不会. 找到了两种解决方案: 通过reqrcode.js,这是一个前端解析二维码内容的js库.如果二维码比较清晰,用这种效果也不错 调用微信扫一扫功能,这种效果很好 ...

  7. .NET Core HttpClient调用腾讯云对象存储Web API的"ERROR_CGI_PARAM_NO_SUCH_OP"问题

    开门见山地说一下问题的原因:调用 web api 时请求头中多了双引号,请求体中少了双引号. 腾讯云提供的对象存储(COS)C# SDK 是基于 .NET Framework 用 WebRequest ...

  8. .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

    .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...

  9. .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

    .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...

随机推荐

  1. Vue Cli 3 报错:router is not defined

    报错内容: 报错原因: 代码全部放在了路由配置的main.js文件里,router没有定义,使用的时候报undefined 解决方法: 把router.beforeEach放在main.js里面

  2. Muduo网络库实战(二):实现服务器与客户端的连接

    1. 方案的确定 1)基本需求 用户1000+, IO压力不大: 多个客户端打开网站,输入查询字符串strclient,发送给服务器=>服务器接收客户端发过来的数据并处理,将结果返回给客户端: ...

  3. ACM及各类程序竞赛专业术语

    AC (Accepted) 程序通过 WA (Wrong Answer) 错误的答案 PE (Presentation Error) 输出格式错误 RE (Runtime Error) 程序执行错误 ...

  4. LCA 学习总结

    怎么说,LCA裸题直接套板子,大家都会做,这样的题没必要看,剩下的题发先LCA只是一个工具就像是搜索一样,只是一个工具而不是一种算法,所以借助这套工具在其图论问题如最长路,数据结构等问题上再去发挥作用 ...

  5. 动态SQL各个标签作用以及注意事项详解

    创建com.mybatis包,包含:UserMapper.xml和mybatis-config.xml UserMapper.xml代码: <?xml version="1.0&quo ...

  6. 基于KepServer实现与S7-1200PLC之间的通信

    对于学习上位机开发,有一种通信方式是必须要了解的,那就是OPC是OLE for Process Control的简称,然而随着技术的不断发展,人们开始对它有了新的定义,比如Open Platform ...

  7. Node.js中模块加载机制

    1.模块查找规则-当模块拥有路径但没有后缀时:(require(‘./find’)) require方法根据模块路径查找模块,如果是完整路径,直接引入模块: 如果模块后缀省略,先找同名JS文件,再找同 ...

  8. CC2530串口通信

    任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX). RX:接收数据串行输入.通过采样技术来区别数据和噪音,从而恢复数据. TX :发送数据输出.当发送器被禁止时,输出引 ...

  9. java线程池原理解析

    五一假期大雄看了一本<java并发编程艺术>,了解了线程池的基本工作流程,竟然发现线程池工作原理和互联网公司运作模式十分相似. 线程池处理流程 原理解析 互联网公司与线程池的关系 这里用一 ...

  10. 【Hadoop离线基础总结】hive的窗口函数

    hive的窗口函数 概述 hive中一般求取TopN时就需要用到窗口函数 专业窗口函数一般有三个 rank() over dense rank() over row_number() over 实例 ...