.net core HttpClient 使用之掉坑解析(一)
一、前言
在我们开发当中经常需要向特定URL地址发送Http请求操作,在.net core 中对httpClient使用不当会造成灾难性的问题,这篇文章主要来分享.net core中通过IHttpClientFactory 工厂来使用HttpClient的正确打开方式。
二、HttpClient使用中的那些坑
2.1 错误使用
using(var client = new HttpClient())
我们可以先来做一个简单的测试,代码如下:
public async Task<string> GetBaiduListAsync(string url)
{
var html = "";
for (var i = 0; i < 10; i++)
{
using (var client = new System.Net.Http.HttpClient())
{
var result=await client.GetStringAsync(url);
html += result;
}
}
return html;
}
运行项目输出结果后,通过netstate查看下TCP连接情况:

虽然项目已经运行结束,但是连接依然存在,状态为" TIME_WAIT"(继续等待看是否还有延迟的包会传输过来;默认在windows下,TIME_WAIT状态将会使系统将会保持该连接 240s。
在高并发的情况下,连接来不及释放,socket被耗尽,耗尽之后就会出现喜闻乐见的一个错误:
错误原因:
对象所占用资源应该确保及时被释放掉,但是,对于网络连接而言,这是错误的,原因有如下:
- 网络连接是需要耗费一定时间的,频繁开启与关闭连接,性能会受影响;
- 开启网络连接时会占用底层socket资源,但在HttpClient调用其本身的Dispose方法时,并不能立刻释放该资源,这意味着你的程序可能会因为耗尽连接资源而产生灾难性的问题。
对于上面的错误原因,大家可能会想到使用静态单例模式的HttpClient,如下:
private static HttpClient Client = new HttpClient();
静态单例模式虽然可以解决上面问题,但是会带来另外一个问题:
- DNS变更会导致不能解析,DNS不会重新加载,需要重启才能变更(有兴趣的大佬可以去尝试一下)
三、正确使用及源码分析

HttpClientFactory 以模块化、可命名、可配置、弹性方式重建了 HttpClient 的使用方式: 由 DI 框架注入 IHttpClientFactory 工厂;由工厂创建 HttpClient 并从内部的 Handler 池分配请求 Handler。
.net core 2.1 开始引入了IHttpClientFactory 工厂类来自动管理IHttpClientFactory 类的创建和资源释放,可以通过Ioc 注入方式进行使用,代码如下:
services.AddControllers();
services.AddHttpClient();
调用代码如下:
private readonly IHttpClientFactory _clientFactory;
public FirstController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
/// <summary>
///
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<string> GetBaiduAsync(string url)
{
var client = _clientFactory.CreateClient();
var result = await client.GetStringAsync(url);
return result;
}
代码中通过IHttpClientFactory 中的CreateClient()方法进行创建一个HttpClient 对象,但是没有看到有释放资源的动作,那它是怎么释放的呢?
我们来看看它的主要源代码
/// <summary>
/// Creates a new <see cref="HttpClient"/> using the default configuration.
/// </summary>
/// <param name="factory">The <see cref="IHttpClientFactory"/>.</param>
/// <returns>An <see cref="HttpClient"/> configured using the default configuration.</returns>
public static HttpClient CreateClient(this IHttpClientFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return factory.CreateClient(Options.DefaultName);
}
public HttpClient CreateClient(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var handler = CreateHandler(name);
var client = new HttpClient(handler, disposeHandler: false);
var options = _optionsMonitor.Get(name);
for (var i = 0; i < options.HttpClientActions.Count; i++)
{
options.HttpClientActions[i](client);
}
return client;
}
public HttpMessageHandler CreateHandler(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;
StartHandlerEntryTimer(entry);
return entry.Handler;
}
代码中可以看到创建HttpClent 时会先创建HttpMessageHandler对象,而CreateHandler 方法中调用了StartHandlerEntryTimer方法,该方法主要时启动清理释放定时器方法,核心代码如下:
public DefaultHttpClientFactory(
IServiceProvider services,
IServiceScopeFactory scopeFactory,
ILoggerFactory loggerFactory,
IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor,
IEnumerable<IHttpMessageHandlerBuilderFilter> filters)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (scopeFactory == null)
{
throw new ArgumentNullException(nameof(scopeFactory));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (optionsMonitor == null)
{
throw new ArgumentNullException(nameof(optionsMonitor));
}
if (filters == null)
{
throw new ArgumentNullException(nameof(filters));
}
_services = services;
_scopeFactory = scopeFactory;
_optionsMonitor = optionsMonitor;
_filters = filters.ToArray();
_logger = loggerFactory.CreateLogger<DefaultHttpClientFactory>();
// case-sensitive because named options is.
_activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
_entryFactory = (name) =>
{
return new Lazy<ActiveHandlerTrackingEntry>(() =>
{
return CreateHandlerEntry(name);
}, LazyThreadSafetyMode.ExecutionAndPublication);
};
_expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>();
_expiryCallback = ExpiryTimer_Tick;
_cleanupTimerLock = new object();
_cleanupActiveLock = new object();
}
// Internal for tests
internal void ExpiryTimer_Tick(object state)
{
var active = (ActiveHandlerTrackingEntry)state;
// The timer callback should be the only one removing from the active collection. If we can't find
// our entry in the collection, then this is a bug.
var removed = _activeHandlers.TryRemove(active.Name, out var found);
Debug.Assert(removed, "Entry not found. We should always be able to remove the entry");
Debug.Assert(object.ReferenceEquals(active, found.Value), "Different entry found. The entry should not have been replaced");
// At this point the handler is no longer 'active' and will not be handed out to any new clients.
// However we haven't dropped our strong reference to the handler, so we can't yet determine if
// there are still any other outstanding references (we know there is at least one).
//
// We use a different state object to track expired handlers. This allows any other thread that acquired
// the 'active' entry to use it without safety problems.
var expired = new ExpiredHandlerTrackingEntry(active);
_expiredHandlers.Enqueue(expired);
Log.HandlerExpired(_logger, active.Name, active.Lifetime);
StartCleanupTimer();
}
// Internal so it can be overridden in tests
internal virtual void StartHandlerEntryTimer(ActiveHandlerTrackingEntry entry)
{
entry.StartExpiryTimer(_expiryCallback);
}
从微软源码分析,HttpClient继承自HttpMessageInvoker,而HttpMessageInvoker实质就是HttpClientHandler。
HttpClientFactory 创建的HttpClient,也即是HttpClientHandler,只是这些个HttpClient被放到了“池子”中,工厂每次在create的时候会自动判断是新建还是复用。(默认生命周期为2min)。
希望这篇文章对你有帮助,如果对你有帮助请点个推荐,感谢!
.net core HttpClient 使用之掉坑解析(一)的更多相关文章
- .net core HttpClient 使用之消息管道解析(二)
一.前言 前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandler和PrimaryHttpMessageHandler ...
- .NET Core HttpClient调用腾讯云对象存储Web API的"ERROR_CGI_PARAM_NO_SUCH_OP"问题
开门见山地说一下问题的原因:调用 web api 时请求头中多了双引号,请求体中少了双引号. 腾讯云提供的对象存储(COS)C# SDK 是基于 .NET Framework 用 WebRequest ...
- Docker从入门到掉坑(三):容器太多,操作好麻烦
前边的两篇文章里面,我们讲解了基于docker来部署基础的SpringBoot容器,如果阅读本文之前没有相关基础的话,可以回看之前的教程. Docker 从入门到掉坑 Docker从入门到掉坑(二): ...
- 开发掉坑(二)前端静态资源 Uncaught SyntaxError: Unexpected token <
某天,有同学反馈后台管理系统出现静态资源无法加载的问题. 复现如下: 进入首页. 点击侧边栏某个子功能,静态资源可正常访问到. 等待10分钟左右,点击侧边栏其他子功能,无法访问到静态资源. 查看控制台 ...
- Gopher必读:HttpClient的两个坑位
http是我们最常见的客户端/服务端传输协议,在golang中,默认的net/http包有一些坑位,需要调整以获得更加性能. 在golang程序中,我也遇到因为不合理使用 http client导致的 ...
- AlvinZH掉坑系列讲解(背包DP大作战H~M)
本文由AlvinZH所写,欢迎学习引用,如有错误或更优化方法,欢迎讨论,联系方式QQ:1329284394. 前言 动态规划(Dynamic Programming),是一个神奇的东西.DP只能意会, ...
- Docker 从入门到掉坑
Docker 介绍 简单的对docker进行介绍,可以把它理解为一个应用程序执行的容器.但是docker本身和虚拟机还是有较为明显的出入的.我大致归纳了一下,可以总结为以下几点: docker自身也有 ...
- Docker从入门到掉坑(二):基于Docker构建SpringBoot微服务
本篇为Docker从入门到掉坑第二篇:基于Docker构建SpringBoot微服务,没有看过上一篇的最好读过 Docker 从入门到掉坑 之后,阅读本篇. 在之前的文章里面介绍了如何基于docker ...
- Docker从入门到掉坑(四):上手k8s避坑指南
在之前的几篇文章中,主要还是讲解了关于简单的docker容器该如何进行管理和操作,在接下来的这篇文章开始,我们将开始进入对于k8s模块的学习 不熟悉的可以先回顾之前的章节,Docker教程系列文章将归 ...
随机推荐
- 前端架构演进及主流UI
@ 目录 前端三要素 JavaScript 框架 NodeJs 常用UI框架 前后端分离的演进 MVVM模式 总结 前端演进到现在,各种技术框架已经层出不穷了,作为一名开发少不了要干一些前端的活儿,那 ...
- Hash记录字符串
Hash记录字符串模板: mod常常取1e9+7,base常常取299,,127等等等....有的题目会卡Hash,因为可能会有两个不同的Hash但却有相通的Hash值...这个时候可以用双Hash来 ...
- 测评软件Lemon教程
Lemon 信息技术测评软件 目录 下载与安装 编译器配置 添加样例和选手 完成测评 1.下载与安装 万恶的 伟大的百度网盘: 链接: https://pan.baidu.com/s/1BfMhs_z ...
- JSP中引用CSS样式文件却无法显示的问题解决方案
你也遇到过这种问题吗,CSS写好了,JSP写好了,在JSP中调用CSS文件,路径检查后也正确,但是无法显示渲染后的页面 原因:罪魁祸首就是过滤器响应数据的时候,响应头设置为了“text/html”,但 ...
- mybatis源码学习:基于动态代理实现查询全过程
前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...
- Xshell下载和连接Linux
Xshell下载和连接Linux 第一步.Xshell的下载 方法1:从官网下载个人使用时免费的,商业使用是要收费的. http://www.xshellcn.com/ 方法二2:百度云下载Xshel ...
- 这是那些大佬程序员常用的学习java网站,这就是别人薪资上万的原因
大学四年,看课本是不可能一直看课本的了,对于学习,特别是自学,善于搜索网上的一些资源来辅助,还是非常有必要的,下面我就把这几年私藏的各种资源,网站贡献出来给你们.主要有:电子书搜索.实用工具.在线视频 ...
- 是时候学习python了
“ 学习Pyhton,如何学以致用 -- 知识往问题靠,问题往知识靠” 01 为什么学Python 一直有听说Python神奇,总是想学,虽然不知道为啥.奈何每天写bug,修bug忙得不亦乐乎,总是不 ...
- Thymeleaf入门入门入门入门入门入门入门入门入门入门入门
Thymeleaf 官网部分翻译:反正就是各种好 Thymeleaf是用来开发Web和独立环境项目的服务器端的Java模版引擎 Spring官方支持的服务的渲染模板中,并不包含jsp.而是Thymel ...
- Qt提示:setLayout: Attempting to set QLayout "" on MainWindow "MainWindow", which already has a layout
如题,出现这个的原因是,如果你的窗口继承的是QMainwindow,需要设置setCentralWidget(); 如下: QWidget * widget = new QWidget ( mainW ...