为IHttpClientFactory添加动态命名配置
某些时候我们需要为HttpClient动态配置一些东西, 例如证书等, 参考博问 如何使用IHttpClientFactory动态添加cer证书. 例如服务是一个回调服务, 而被回调方采用了自定义的https(即自定义证书).
上述是一些前情概要, 那么接下来我们就来实现这个需求.
秒想到一个方法, 我们可以直接new HttpClient()
, 在每一次要使用的时候都直接来一个, 简单粗暴.
秒想到第二个方法, 又或者用一个Dictionary<string,HttpClient>
根据名字缓存client对象.
但是前者性能是个问题,而且涉及到端口的占用释放问题, 在调用量稍大的情况下得凉凉, 后者则是有已知的问题HttpClient对象没法感知dns的变更.
其他一些更不靠谱的方法还有: 使用代码配置方式(services.AddHttpClient("callback provider side").ConfigurePrimaryHttpMessageHandler()
)配置所有证书, 还有把所有证书都安装的本机上并设置为信任证书.
那么能除了上面这些不靠谱的方式(或者说有致命缺陷的方式), 还有靠谱的么, 那当然是有的, 例如运行时的动态配置实现方案.
所以, 接下来, 我来推荐 2 种方式式,就是我们的IHttpMessageHandlerBuilderFilter
和IPostConfigureOptions
.
官方有什么推荐么?
针对如何为HttpClient对象添加证书, 官方文档的实现是:使用证书和来自 IHttpClientFactory 的命名 HttpClient 实现 HttpClient 和 使用证书和 HttpClientHandler 实现 HttpClient, 但是在这里显然没法解决我们的运行时配置的需求, 但是它给出了一条线索, 那就是命名配置. 它可以为我们的每一个不同的provider提供自定义配置. 只要我们能为每一个不同的provider能提供运行时配置即可, 接下来就是源码阅读时间了:
下文中的所有代码都来自netcore 3.1, 并且仅copy关键代码, 完整代码可以前往github查看.
IHttpClientFactory.CreateClient是如何将HttpClient创建出来的?
- 每次
CreateClient
出来的都是一个新的HttpClient
实例 - 在
CreateHandler
中的_activeHandlers
将为我们缓存我们的handler, 默认是2分钟(定义在HttpClientFactoryOptions.HandlerLifetime
)- 这里有一个知识点就是如果我的请求刚好在过期时间前一点点获取到这个缓存的对象,就是有可能我当前的请求还在进行中, 但是2分钟过去后这个handler就要被回收的. 那官方是如何替我们解决这个可能的bug的呢, 请查看文章Cleaning up expired handlers, 我就不赘述了, 关键点在于用了一个
WeakReference
- 这里有一个知识点就是如果我的请求刚好在过期时间前一点点获取到这个缓存的对象,就是有可能我当前的请求还在进行中, 但是2分钟过去后这个handler就要被回收的. 那官方是如何替我们解决这个可能的bug的呢, 请查看文章Cleaning up expired handlers, 我就不赘述了, 关键点在于用了一个
CreateHandlerEntry
方法则是真正的创建以及配置我们的handlers的地方.- 从
IConfiguration
获得一个HttpClientFactoryOptions
对象 - 应用
IHttpMessageHandlerBuilderFilter
- 应用
HttpMessageHandlerBuilderActions
- 从
//Microsoft.Extensions.Http.DefaultHttpClientFactory
public HttpClient CreateClient(string name)
{
HttpClient httpClient = new HttpClient(this.CreateHandler(name), disposeHandler: false);
return httpClient;
}
public HttpMessageHandler CreateHandler(string name)
{
ActiveHandlerTrackingEntry value = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value;
//_entryFactory可以直接理解为是CreateHandlerEntry方法.它真实的类型是Lazy<>(CreateHandlerEntry,LazyThreadSafetyMode.ExecutionAndPublication)的, 也就是并发安全的调用CreateHandlerEntry.
return value.Handler;
}
internal ActiveHandlerTrackingEntry CreateHandlerEntry(string name)
{
HttpClientFactoryOptions options = this._optionsMonitor.Get(name);
HttpMessageHandlerBuilder requiredService = provider.GetRequiredService<HttpMessageHandlerBuilder>();
requiredService.Name = name;
Action<HttpMessageHandlerBuilder> action = Configure; // 扩展点二 HttpClientFactoryOptions.HttpMessageHandlerBuilderActions
for (int num = this._filters.Length - 1; num >= 0; num--)
{
action = this._filters[num].Configure(action); //扩展点一 _filters(构造函数传入的IEnumerable<IHttpMessageHandlerBuilderFilter> filters).
}
action(requiredService);
LifetimeTrackingHttpMessageHandler handler = new LifetimeTrackingHttpMessageHandler(requiredService.Build());
return new ActiveHandlerTrackingEntry(name, handler, serviceScope, options.HandlerLifetime);
void Configure(HttpMessageHandlerBuilder b)
{
for (int i = 0; i < options.HttpMessageHandlerBuilderActions.Count; i++)
{
options.HttpMessageHandlerBuilderActions[i](b);
}
}
}
关键点代码就是上面代码中标记出来的扩展点一
和 扩展点二
.
- 扩展点一: 需要注入适当的IHttpMessageHandlerBuilderFilter对象,就可以改写
requiredService
对象, 也就可以实现我们要的运行时动态配置了. - 扩展点二: 需要实现自定义的IConfiguration配置, 只要
this._optionsMonitor.Get(name)
拿到的对象的HttpMessageHandlerBuilderActions
属性包含我们相应的改写代码即可.
扩展点一的实现
为HttpClient的handler增加一个配置的filter, 针对符合的handlerBuilder增加一些自己的改写逻辑.
我们在用HttpClient对象的时候产生的日志("Sending HTTP request......","Received HTTP response headers after......")就是由这个Filter特性注入的. 官方参考代码:LoggingHttpMessageHandlerBuilderFilter
个人见解: 觉得在这个扩展点加这个业务不是特别的符合应用场景, 所以我建议在扩展点二做这个事情.
class MyHttpClientHandlerFilter : IHttpMessageHandlerBuilderFilter
{
public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
{
void Configure(HttpMessageHandlerBuilder builder)
{
next(builder); //一开始就调用next, 这样我们的整个HandlerBuilder的执行顺序就是依次call _filters, 最后call options.HttpMessageHandlerBuilderActions(扩展点二).
if (builder.Name.StartsWith("CallbackProviderSide-")) //我们可以为这类业务统一加一个前缀做区别, 这样就不会影响其他的HttpClient对象了.
{
//builder.PrimaryHandler= your custom handler. 参考官方文档的实现.
}
}
return Configure;
}
}
//然后在DI容器中注入我们的filter.
ServiceCollection.AddSingleton<IHttpMessageHandlerBuilderFilter,MyHttpClientHandlerFilter>();
扩展点二的实现
class MyHttpClientCustomConfigure : IPostConfigureOptions<HttpClientFactoryOptions>
{
public void PostConfigure(string name, HttpClientFactoryOptions options)
{
if (name.StartsWith("CallbackProviderSide-")) //我们可以为这类业务统一加一个前缀做区别, 这样就不会影响其他的HttpClient对象了.
{
options.HttpMessageHandlerBuilderActions.Add(p =>
{
//p.PrimaryHandler= your custom handler. 参考官方文档的实现.
});
}
}
}
//然后在DI容器中注入我们的这个配置扩展类.
ServiceCollection.AddSingleton<Microsoft.Extensions.Options.IPostConfigureOptions<Microsoft.Extensions.Http.HttpClientFactoryOptions>, MyHttpClientCustomConfigure>();
为什么这里注入的类型是Microsoft.Extensions.Options.IPostConfigureOptions<Microsoft.Extensions.Http.HttpClientFactoryOptions>
, 是因为OptionsFactory
它的构造函数需要的就是这个. 至于有关Configuration系统的扩展和源代码在这里就不在这里展开了.
使用
至于用它就简单了
var factory = ServiceProvider.GetService<IHttpClientFactory>();
var httpClientForBaidu = factory.CreateClient("CallbackProviderSide-baidu");
var httpClientForCnblogs = factory.CreateClient("CallbackProviderSide-Cnblogs");
总结一下
这样子, 我们的这个运行时动态配置HttpClient就算完成了, 我也轻轻松松又水了一篇文章.
另外,有关IHttpClientFactory
背后的故事可以查看文章Exploring the code behind IHttpClientFactory in depth, 很完整的流程图在配上代码, 把它讲解的清清楚楚.
为IHttpClientFactory添加动态命名配置的更多相关文章
- 【干货干货】hyperledger fabric 之动态添加组织/修改配置 (Fabric-java-sdk) 下
我们接着上一节来讲: 在熟悉动态增加组织或修改配置的步骤后,我们就可以使用java的api来完成动态增加组织或修改配置了: 废话不多说,直接上干货: 1,预制条件 org3的证书以及组织3的MSP详情 ...
- Spring Boot2.x 动态数据源配置
原文链接: Spring Boot2.x 动态数据源配置 基于 Spring Boot 2.x.Spring Data JPA.druid.mysql 的动态数据源配置Demo,适合用于数据库的读写分 ...
- MFC如何在树形图边上添加动态小地图
MFC如何在树形图边上添加动态小地图 https://www.jianshu.com/p/7b1d828bf5db (简书无法识别缩进的...早知道先在博客园发了) (转载请注明出处) 作者:梦镜谷雨 ...
- springboot+shiro+redis(单机redis版)整合教程-续(添加动态角色权限控制)
相关教程: 1. springboot+shiro整合教程 2. springboot+shiro+redis(单机redis版)整合教程 3. springboot+shiro+redis(集群re ...
- vue微信分享链接添加动态参数
微信分享时 分享链接携带参数可能不是固定的 需要在分享的前一刻才知道 这里就是动态设置分享链接的基本写法 代码不是那么详尽 但大致流程如下 1.安装引用jssdk npm install --save ...
- spring boot 整合 quartz 集群环境 实现 动态定时任务配置【原】
最近做了一个spring boot 整合 quartz 实现 动态定时任务配置,在集群环境下运行的 任务.能够对定时任务,动态的进行增删改查,界面效果图如下: 1. 在项目中引入jar 2. 将需要 ...
- Httpd服务入门知识-Httpd服务常见配置案例之DSO( Dynamic Shared Object)加载动态模块配置
Httpd服务入门知识-Httpd服务常见配置案例之DSO( Dynamic Shared Object)加载动态模块配置 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.加载动 ...
- 如何利用腾讯云COS为静态博客添加动态相册
前言 本文首发于个人网站Jianger's Blog,欢迎访问订阅.个人博客小站刚建站不久,想着除了主题里的功能外再添加上相册模块,于是半搜索半摸索把相册模块搞出来了,最后采用了利用腾讯云对象存储作图 ...
- 动态主机配置协议-DHCP
一.DHCP 概述 当局域网中有大量的PC时.如果我们逐个为每台PC去手动配置IP.那这就是一个吃力也未必讨好的办法 累死你 而DHCP 刚好可以解决这个问题.DHCP全称(动态主机配置协议).使用的 ...
随机推荐
- PowerShell-5.网络请求
写了一个函数,用于文件下载并且执行: function DownLoadAndRun($vUrl ,$vLocalPath ,$vStart){ Import-Module BitsTransfer ...
- nodejs-Stream(流)
Node.js Stream(流) 描述++++++++++++++++++++++++++++++++++++++++++++++++++++ Stream 是一个抽象接口,Node 中有很多对象实 ...
- ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(上)
快速上手多人游戏服务器开发.后续会基于 Google Agones,更新相关 K8S 运维.大规模快速扩展专用游戏服务器的文章.拥抱️原生 Cloud-Native! 快速开始 在开始之前,让我们确保 ...
- js--吐血总结最近遇到的变态表单校验---element+原生+jq+easyUI(前端职业生涯见过的最烦的校验)
最近写了无数各种形式的表单,记录下奇奇怪怪的校验规则~ 一:首先是element自带的rules校验规则: element作为常用框架,自带rules属性简单易懂,官方文档一目了然,不再赘述,应付常用 ...
- XD to Flutter 2.0 现已发布!
Flutter 是 Google 的开源 UI 工具包.利用它,只需一套代码库,就能开发出适合移动设备.桌面设备.嵌入式设备以及 web 等多个平台的精美应用.过去几年,对于想要打造多平台应用的开发者 ...
- 一文读懂 SuperEdge 云边隧道
作者 李腾飞,腾讯容器技术研发工程师,腾讯云TKE后台研发,SuperEdge核心开发成员. 杜杨浩,腾讯云高级工程师,热衷于开源.容器和Kubernetes.目前主要从事镜像仓库,Kubernete ...
- Pytorch_Part3_模型模块
VisualPytorch beta发布了! 功能概述:通过可视化拖拽网络层方式搭建模型,可选择不同数据集.损失函数.优化器生成可运行pytorch代码 扩展功能:1. 模型搭建支持模块的嵌套:2. ...
- work2_求交点数
教学班级:周三上午三四节 项目地址:https://github.com/875571216/- PSP表格 psp2.1 Personal Software Process Stages 预估耗时( ...
- 《Ray Tracing in One Weekend》阅读笔记 - 9、Metal(金属)
如果我们希望不同的物体使用不同的材料,则需要进行设计决策.我们可以使用具有许多参数的通用材料,而将不同的材料类型仅将其中一些参数归零.这不是一个坏方法.或者我们可以有一个抽象的材料类来封装行为.我是后 ...
- 戴尔服务器如何配置远程管理卡(IDRAC9)适用于戴尔R740服务器
戴尔服务器如何配置远程管理卡(IDRAC9)适用于戴尔R740服务器 转: DELL IDRAC9 该配置方法适合于所有戴尔14G服务器,包括全系列戴尔服务器,标准版适用于R440/R540/R640 ...