.net core 中构造函数注入 IHttpClientFactory 和 HttpClient 的区别,使用 HttpClient 注入有什么隐患,以及如何证明?

部署运行你感兴趣的模型镜像

.net core 中构造函数注入 IHttpClientFactoryHttpClient 的区别有哪些,使用 HttpClient 注入有什么隐患,以及如何证明?

先说推荐

官方文档介绍的大概意思如下:

HttpClient 类使用比较简单,但在某些情况下,许多开发人员却并未正确使用该类;虽然此类实现 IDisposable,但在 using 语句中声明和实例化它并非首选操作,因为释放 HttpClient 对象时,基础套接字不会立即释放,这可能会导致套接字耗尽问题,最终可能会导致 SocketException 错误。要解决此问题,推荐的方法是将 HttpClient 对象创建为单一对象或静态对象。

简单的说直接创建使用 HttpClient 对象,不能立即关闭连接、性能消耗严重等的问题。.NET Core 2.1+ 开始引入的 HttpClientFactory 解决了 HttpClient 的所有痛点。

IHttpClientFactory 是在 .NETCore 2.1 开始提供的,默认实现为 DefaultHttpClientFactory,专门用于创建在应用程序中用到的 HttpClient 实例,自动维护内部的 HttpMessageHandler 池及其生命周期。

HttpClientFactory

从微软源码分析,HttpClient 继承自 HttpMessageInvoker,而HttpMessageInvoker 实质就是 HttpClientHandler

HttpClientFactory 创建的 HttpClient,也即是 HttpClientHandler,只是这些个 HttpClient 被放到了 Pool “池子” 中,工厂每次在 create 的时候会自动判断是新建还是复用(默认生命周期为 2min)。

使用 IHttpClientFactory 的好处

同时实现 IHttpMessageHandlerFactoryIHttpClientFactory 当前实现具有以下优势:

  • 提供一个中心位置,用于命名和配置逻辑 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 文件添加服务

  • 项目安装 nugetMicrosoft.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);
  }
}

优点

  1. 直接使用:直接注入一个已经配置好的 HttpClient 实例,使用起来更加直观简洁,无需额外的工厂方法调用。

隐患

  1. 资源泄漏:如果在整个应用程序生命周期内保持单个 HttpClient 实例的静态或单例模式,可能会导致 TCP 连接资源无法释放,尤其是在高并发场景下,可能导致端口耗尽。
  2. DNS 刷新问题:如前所述,静态或单例的 HttpClient 实例不会自动更新 DNS 解析结果,当目标服务器 IP 发生变化时,应用可能无法正确连接到服务。
  3. 配置局限:直接注入的 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);
  }
}

优点:

  1. 资源管理优化IHttpClientFactory 作为专门设计用于管理 HttpClient 实例的工厂类,遵循最佳实践,能够有效地复用HttpClient 底层的 TCP 连接,避免因频繁创建和销毁 HttpClient 实例导致的端口耗尽问题。
  2. DNS刷新IHttpClientFactory 创建的 HttpClient 实例能够自动响应 DNS 更改,解决了静态或单例 HttpClient 不刷新 DNS 缓存可能导致的问题。
  3. 可配置性与扩展性:通过使用IHttpClientFactory,可以轻松地为不同的服务或 API 定义命名的客户端,每个客户端都可以独立配置(如超时、重试策略、消息处理器等)。此外,还可以使用 Polly 等库进行更复杂的故障恢复和熔断策略。
  4. 生命周期管理IHttpClientFactory 遵循依赖注入容器的生命周期,可以根据需要创建和释放 HttpClient 实例,减轻开发者对 HttpClient 生命周期管理的负担。

推荐使用:由于上述诸多优势,特别是在需要频繁或长期执行 HTTP 请求的场景中,强烈推荐使用 IHttpClientFactory 来管理 HttpClient 的创建和使用。

4、如何证明 HttpClient 隐患

要证明直接注入 HttpClient 的隐患,可以通过以下方式:

  1. 端口耗尽模拟:编写一个程序,直接注入并频繁创建新的HttpClient 实例(或长时间保持单个实例但进行大量并发请求)。监控系统资源,特别是网络端口使用情况。在高并发请求下,可能会观察到可用端口数量快速减少直至耗尽,这证明了资源管理问题的存在。

  2. 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 状态下等待 2MSLmaximum segment lifetime)后才能释放连接(端口),目的是为了在 TCP 四次挥手关闭连接机制中,保证 ACK 重发和丢弃延迟数据。按照解释来看,这种做法也算是合情合理,保证数据传输嘛,但的确就是占用资源啦;具体关于网络的相关知识,小伙伴们再去查阅一下。

3、对比 HashCode

除了上面的测试方式,我们还可以简单的对比下构造函数 DI 注入 HttpClientIHttpClientFactory 对象分别生产 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 和构造函数 DIHttpClient 对象,它们的哈希代码是一样的。

  • 使用 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
  • 《使用 .NETIHttpClientFactory》,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

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

<think>好的,我现在要为用户介绍在.NET Core ABP框架中如何注入使用IHttpClientFactory。首先,我需要回想一下ABP框架的基本结构依赖注入机制。ABP基于ASP.NET Core,所以应该支持原生的IHttpClientFactory,但可能有一些ABP特有的集成方式或者最佳实践。 用户可能已经知道IHttpClientFactory在普通ASP.NET Core中的用法,比如通过AddHttpClient来注册,然后通过构造函数注入IHttpClientFactory实例,再使用CreateClient方法。但在ABP框架中,可能推荐使用模块化的方式配置,或者利用ABP的依赖注入系统来管理HttpClient的生命周期。 接下来,我需要确认ABP是否对IHttpClientFactory有封装或扩展。查阅ABP的文档,发现ABP确实提供了一些便捷的扩展方法,比如在模块的ConfigureServices方法中使用Configure<AbpHttpClientOptions>来配置默认的HttpClient,或者为特定服务配置命名客户端。这类似于原生的方式,但可能集成得更紧密。 然后,我需要考虑如何在ABP中正确注册HttpClient服务。例如,在应用层的模块中,通过PreConfigureServices或ConfigureServices方法添加HttpClient的配置。可能需要使用AddHttpClient扩展方法,并指定客户端名称相关配置,比如设置基地址、超时时间、默认请求头等。 另外,ABP可能有自己的HTTP客户端代理生成功能,特别是在需要调用远程服务时,比如动态Web API客户端。这可能与IHttpClientFactory使用场景不同。用户的问题是关于直接使用IHttpClientFactory注入,所以可能需要区分这两种情况,避免混淆。 接下来,我需要思考如何在服务类中注入IHttpClientFactory。按照ASP.NET Core的标准做法,在构造函数中添加参数,ABP的依赖注入系统会自动解析。但需要确保在模块中已经正确配置了HttpClient服务,否则可能会抛出异常。 还需要提醒用户注意HttpClient的生命周期管理,避免套接字耗尽问题。IHttpClientFactory正是为了解决这个问题而设计的,所以在ABP中使用时,应该遵循相同的准则,比如避免手动实例化HttpClient,而是通过工厂来创建。 可能还需要给出一个代码示例,说明在ABP模块中配置HttpClient,然后在领域服务或应用服务中注入IHttpClientFactory使用。例如: 在模块类中: ConfigureServices(ServiceConfigurationContext context) { context.Services.AddHttpClient("MyClient", client => { client.BaseAddress = new Uri("https://api.example.com"); }); } 然后在服务类中: public class MyService : ITransientDependency { private readonly IHttpClientFactory _httpClientFactory; public MyService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task CallApiAsync() { var client = _httpClientFactory.CreateClient("MyClient"); // 使用client发送请求 } } 此外,可能还需要提到ABP中的其他相关功能,比如集成Polly进行重试策略,或者如何结合ABP的授权系统自动添加Bearer Token到请求头中,这些可以通过配置HttpClient的Handlers来实现。 最后,总结在ABP中使用IHttpClientFactory的步骤:配置HttpClient服务,注入IHttpClientFactory,创建客户端并发送请求。同时,强调遵循ABP的模块化设计原则,将相关配置放在对应的模块中,保持代码组织清晰。</think>在 .NET Core 的 ABP 框架中,使用 `IHttpClientFactory` 进行 HTTP 客户端管理是推荐做法,它能有效解决 `HttpClient` 实例化及生命周期问题。以下是分步说明: --- ### **1. 配置 HttpClient 服务** 在 ABP 模块的 `ConfigureServices` 方法中注册命名或类型化客户端: ```csharp public override void ConfigureServices(ServiceConfigurationContext context) { // 注册命名客户端 context.Services.AddHttpClient("MyApiClient", client => { client.BaseAddress = new Uri("https://api.example.com"); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.Timeout = TimeSpan.FromSeconds(30); }); // 或注册类型化客户端(推荐) context.Services.AddHttpClient<IMyService, MyService>(client => { client.BaseAddress = new Uri("https://api.example.com"); }); } ``` --- ### **2. 注入 IHttpClientFactory** 在服务类构造函数中直接注入 `IHttpClientFactory`: ```csharp public class MyAppService : ApplicationService, ITransientDependency { private readonly IHttpClientFactory _httpClientFactory; public MyAppService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } } ``` --- ### **3. 使用 HttpClient** 通过工厂创建客户端并发送请求: ```csharp public async Task<string> GetDataAsync() { // 使用命名客户端 var client = _httpClientFactory.CreateClient("MyApiClient"); var response = await client.GetAsync("/data"); return await response.Content.ReadAsStringAsync(); } ``` --- ### **4. 类型化客户端(推荐)** 定义接口实现类,更优雅地封装 HTTP 调用: ```csharp public interface IMyService { Task<string> GetDataAsync(); } public class MyService : IMyService, ITransientDependency { private readonly HttpClient _httpClient; public MyService(HttpClient httpClient) { _httpClient = httpClient; _httpClient.BaseAddress = new Uri("https://api.example.com"); } public async Task<string> GetDataAsync() { return await _httpClient.GetStringAsync("/data"); } } ``` --- ### **5. 集成 Polly 策略** 在 ABP 中可轻松集成重试、熔断等策略: ```csharp context.Services.AddHttpClient("RetryClient") .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))); ``` --- ### **6. 自动添加认证头** 结合 ABP 身份认证,自动附加 Bearer Token: ```csharp context.Services.AddHttpClient("SecureClient") .AddHttpMessageHandler(provider => { var tokenProvider = provider.GetRequiredService<ICurrentPrincipalAccessor>(); return new AuthenticationHeaderHandler(tokenProvider); }); // 自定义 Handler public class AuthenticationHeaderHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = await GetTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); return await base.SendAsync(request, cancellationToken); } } ``` --- ### **关键点总结** - **模块化配置**:在 ABP 模块中统一管理 HttpClient 配置。 - **依赖注入**:直接注入 `IHttpClientFactory` 或类型化客户端。 - **生命周期**:无需手动释放 `HttpClient`,由工厂管理。 - **扩展性**:结合 Polly、ABP 认证等增强 HTTP 功能。 这种方法确保了代码的模块化可维护性,同时遵循 ABP 框架的设计规范。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChaITSimpleLove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值