.net core中构造函数注入IHttpClientFactory和HttpClient的区别有哪些,使用HttpClient注入有什么隐患,以及如何证明?
构造函数注入 IHttpClientFactory 和 HttpClient 的区别
先说推荐
官方文档介绍的大概意思如下:
HttpClient类使用比较简单,但在某些情况下,许多开发人员却并未正确使用该类;虽然此类实现IDisposable,但在using语句中声明和实例化它并非首选操作,因为释放HttpClient对象时,基础套接字不会立即释放,这可能会导致套接字耗尽问题,最终可能会导致SocketException错误。要解决此问题,推荐的方法是将HttpClient对象创建为单一对象或静态对象。
简单的说直接创建使用 HttpClient 对象,不能立即关闭连接、性能消耗严重等的问题。.NET Core 2.1+ 开始引入的 HttpClientFactory 解决了 HttpClient 的所有痛点。
IHttpClientFactory 是在 .NETCore 2.1 开始提供的,默认实现为 DefaultHttpClientFactory,专门用于创建在应用程序中用到的 HttpClient 实例,自动维护内部的 HttpMessageHandler 池及其生命周期。

从微软源码分析,HttpClient 继承自 HttpMessageInvoker,而HttpMessageInvoker 实质就是 HttpClientHandler。
HttpClientFactory 创建的 HttpClient,也即是 HttpClientHandler,只是这些个 HttpClient 被放到了 Pool “池子” 中,工厂每次在 create 的时候会自动判断是新建还是复用(默认生命周期为 2min)。
使用 IHttpClientFactory 的好处
同时实现 IHttpMessageHandlerFactory 的 IHttpClientFactory 当前实现具有以下优势:
- 提供一个中心位置,用于命名和配置逻辑
HttpClient对象。 例如,可以配置预配置的客户端(服务代理)以访问特定微服务。 - 通过后列方式整理出站中间件的概念:在
HttpClient中委托处理程序并实现基于Polly的中间件以利用Polly的复原策略。 HttpClient已经具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站HTTP请求。 将HTTP客户端注册到工厂后,可使用一个Polly处理程序将Polly策略用于重试、断路器等。- 管理
HttpMessageHandler的生存期,避免在自行管理HttpClient生存期时出现上述问题。
官方文档:
- https://learn.microsoft.com/zh-cn/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
Service 服务准备(模拟外部接口服务)
此处使用 asp.net core webapi 默认(WeatherForecast)接口:
using Microsoft.AspNetCore.Mvc;
namespace WebApp1.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 3).Select(index => new WeatherForecast {
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray();
}
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
返回数据格式如下:
[
{
"date": "2024-04-16",
"temperatureC": -11,
"temperatureF": 13,
"summary": "Scorching"
},
{
"date": "2024-04-17",
"temperatureC": -18,
"temperatureF": 0,
"summary": "Balmy"
},
{
"date": "2024-04-18",
"temperatureC": 46,
"temperatureF": 114,
"summary": "Mild"
}
]
模拟外部服务接口正常就绪。
构造函数注入 HttpClient & IHttpClientFactory 对比
1、Program.cs 文件添加服务
- 项目安装
nuget包Microsoft.Extensions.Http.Polly; - 添加
AddHttpClient命名服务,并使用TransientHttpErrorPolicy策略;
string clientName = "local";
string uri = "http://localhost:5234/";
builder.Services.AddHttpClient(clientName, client => {
client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/vnd.github.v3+json");
client.DefaultRequestHeaders.TryAddWithoutValidation("client-name", clientName);
}).AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)
}));
说明:此处为了方便,统一配置如上所示,如果只是单纯使用 HttpClient 方法,直接 builder.Services.AddHttpClient() 即可。
2、构造函数注入 HttpClient
- 示例
public class SomeController(ILogger<SomeController> logger,
HttpClient httpClient): ControllerBase
{
[HttpGet]
public async Task<IActionResult> SomeActionAsync()
{
logger.LogInformation($"time={DateTime.Now},使用 HttpClient");
string uri = "http://localhost:5234/";
httpClient.BaseAddress = new Uri(uri);
var resp = await httpClient.GetStringAsync("/WeatherForecast");
//httpClient.Dispose();
return Ok(resp);
}
}
优点:
- 直接使用:直接注入一个已经配置好的
HttpClient实例,使用起来更加直观简洁,无需额外的工厂方法调用。
隐患:
- 资源泄漏:如果在整个应用程序生命周期内保持单个
HttpClient实例的静态或单例模式,可能会导致TCP连接资源无法释放,尤其是在高并发场景下,可能导致端口耗尽。 - DNS 刷新问题:如前所述,静态或单例的
HttpClient实例不会自动更新DNS解析结果,当目标服务器IP发生变化时,应用可能无法正确连接到服务。 - 配置局限:直接注入的
HttpClient实例通常具有固定配置,难以根据不同场景或服务需求动态调整。
不推荐使用:考虑到潜在的资源管理问题和可扩展性限制,一般情况下不推荐直接在构造函数中注入HttpClient实例。
3、构造函数注入 IHttpClientFactory
- 示例
public class SomeController(ILogger<SomeController> logger,
IHttpClientFactory httpClientFactory) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> SomeActionAsync()
{
logger.LogInformation($"time={DateTime.Now},使用 IHttpClientFactory");
var httpClient = httpClientFactory.CreateClient("local");
var resp = await httpClient.GetStringAsync("/WeatherForecast");
return Ok(resp);
}
}
优点:
- 资源管理优化:
IHttpClientFactory作为专门设计用于管理HttpClient实例的工厂类,遵循最佳实践,能够有效地复用HttpClient底层的TCP连接,避免因频繁创建和销毁HttpClient实例导致的端口耗尽问题。 - DNS刷新:
IHttpClientFactory创建的HttpClient实例能够自动响应DNS更改,解决了静态或单例HttpClient不刷新DNS缓存可能导致的问题。 - 可配置性与扩展性:通过使用
IHttpClientFactory,可以轻松地为不同的服务或API定义命名的客户端,每个客户端都可以独立配置(如超时、重试策略、消息处理器等)。此外,还可以使用Polly等库进行更复杂的故障恢复和熔断策略。 - 生命周期管理:
IHttpClientFactory遵循依赖注入容器的生命周期,可以根据需要创建和释放HttpClient实例,减轻开发者对HttpClient生命周期管理的负担。
推荐使用:由于上述诸多优势,特别是在需要频繁或长期执行 HTTP 请求的场景中,强烈推荐使用 IHttpClientFactory 来管理 HttpClient 的创建和使用。
4、如何证明 HttpClient 隐患
要证明直接注入 HttpClient 的隐患,可以通过以下方式:
-
端口耗尽模拟:编写一个程序,直接注入并频繁创建新的
HttpClient实例(或长时间保持单个实例但进行大量并发请求)。监控系统资源,特别是网络端口使用情况。在高并发请求下,可能会观察到可用端口数量快速减少直至耗尽,这证明了资源管理问题的存在。 -
DNS刷新测试:设置一个测试环境,使得目标服务器的IP地址可以在运行时动态变更。编写一个使用静态或单例HttpClient实例的应用程序,持续向该服务器发送请求。当服务器IP变更后,观察应用程序是否能及时适应新的IP地址。如果请求仍然发往旧IP,说明HttpClient未能自动刷新DNS。
说明:代码中访问的地址(
http://localhost:5234/WeatherForecast)是用本地dotnet run服务(也可以在阿里云上Nginx的服务)站点,此处没有做负载,所以看Socket状态比较直观。
上面代码执行完成之后就退出了,理论情况来说,上面程序执行完毕之后,就不应该占用资源啦,但通过 netstat 查看,的确还有 Socket 被占用,测试如下:
Service 测试
1、Windows 环境
运行服务:
# 进入到对应的服务目录
dotnet run
查看端口占用情况:
netstat -ano | findstr TIME_WAIT | findstr 127.0.0.1
输出信息如下:

2、Linux 环境
把 .Net Core 运行环境安装好,创建一个目录,将编译之后的文件通过 Xftp/FileZilla Client 将文件传到云服务器,执行以下命令即可:
# 注意,这里指定启动的是 dll 文件
dotnet HttpClientDemo.dll
查看端口占用情况:
# 查看端口占用情况,找到状态为 TIME_WAIT
netstat -ant | grep TIME_WAIT | grep 127.0.0.1
感兴趣的请自行验证,此处就不在截图演示了。
说明:
TIME_WAIT是主动关闭TCP连接方出现的状态,系统会在TIME_WAIT状态下等待2MSL(maximum segment lifetime)后才能释放连接(端口),目的是为了在TCP四次挥手关闭连接机制中,保证ACK重发和丢弃延迟数据。按照解释来看,这种做法也算是合情合理,保证数据传输嘛,但的确就是占用资源啦;具体关于网络的相关知识,小伙伴们再去查阅一下。
3、对比 HashCode
除了上面的测试方式,我们还可以简单的对比下构造函数 DI 注入 HttpClient 和 IHttpClientFactory 对象分别生产 httpclient 的哈希码 GetHashCode() 。
using Microsoft.AspNetCore.Mvc;
namespace HttpClientDemo.Controllers;
[Route("api/[controller]/[action]")]
[ApiController]
public class DemoController(HttpClient httpClient, IHttpClientFactory httpClientFactory) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetHttpClientHashCodeAsync()
{
var hc1 = httpClient.GetHashCode();
var httpClient2 = httpClientFactory.CreateClient();
var hc2 = httpClient2.GetHashCode();
var httpClient3 = httpClientFactory.CreateClient("local");
var hc3 = httpClient3.GetHashCode();
await Task.CompletedTask;
return Ok($"hc1={hc1},hc2={hc2},hc3={hc3}");
}
}
输出信息:
hc1=41300193,hc2=15586314,hc3=35059110
通过上面三种方式创建的对象,分别获取哈希代码均不相同,说明他们不是同一个对象,如果使用 DefaultHttpClientFactory 对象创建的 HttpClient 和构造函数 DI 的 HttpClient 对象,它们的哈希代码是一样的。
- 使用
IHttpClientFactory创建HttpClient的两种方式。
namespace System.Net.Http
{
public static class HttpClientFactoryExtensions
{
public static HttpClient CreateClient(this IHttpClientFactory factory);
}
public interface IHttpClientFactory
{
HttpClient CreateClient(string name);
}
}
推荐使用 IHttpClientFactory
综上所述,出于性能、资源管理、可配置性和扩展性的考虑,推荐在 .NET Core 中使用构造函数注入 IHttpClientFactory 而非直接注入 HttpClient。若要证明直接注入 HttpClient 的隐患,可以通过模拟高并发场景引发端口耗尽问题,以及进行 DNS 刷新测试来验证其对 DNS 变更的响应能力。
注意:
IHttpClientFactory创建的HttpClient实例的生存期管理与手动创建的实例完全不同。 策略是使用由IHttpClientFactory创建的短期客户端,或设置了PooledConnectionLifetime的长期客户端。 有关详细信息,请参阅 《HttpClient生存期管理》部分和《使用HttpClient的指南》。
- 《
HttpClient生存期管理》,https://learn.microsoft.com/zh-cn/dotnet/core/extensions/httpclient-factory#httpclient-lifetime-management - 《
HttpClient的使用准则/指南》,https://learn.microsoft.com/zh-cn/dotnet/fundamentals/networking/http/httpclient-guidelines
相关博文参考:
- 《使用
IHttpClientFactory实现复原HTTP请求》,https://learn.microsoft.com/zh-cn/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - 《使用
.NET的IHttpClientFactory》,https://learn.microsoft.com/zh-cn/dotnet/core/extensions/httpclient-factory - 《
.net core HttpClient使用之掉坑解析(一)》,https://www.cnblogs.com/jlion/p/12813692.html - 《把
HttpClient换成IHttpClientFactory之后,放心多了》,https://zhuanlan.zhihu.com/p/381738224
873

被折叠的 条评论
为什么被折叠?



