为什么不能每次 new HttpClient()?
直接
new HttpClient()会导致端口耗尽、DNS 缓存失效、连接复用失败等问题。.NET 的
HttpClient是线程安全且设计为长期复用的,频繁创建销毁会触发底层
Socket资源泄漏(尤其在高并发下),错误日志里常出现
SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full。
根本原因不是
HttpClient本身没 Dispose,而是它内部的
HttpMessageHandler(默认是
HttpClientHandler)持有连接池和 DNS 缓存,过早释放会打断复用链路。
HttpClientFactory 是怎么解决这个问题的?
HttpClientFactory不是帮你“创建 HttpClient”,而是管理背后的
HttpMessageHandler生命周期,并按需提供轻量级、短命的
HttpClient实例(每个实例只持有一个 handler 引用,不拥有 handler)。handler 由工厂统一维护,默认 2 分钟空闲后回收,支持 DNS 刷新、连接池自动调优。
使用时你拿到的是“借用”的
HttpClient,不用
Dispose,也不该缓存它——下次
GetClient("api") 拿到的可能是新实例,但底层 handler 很可能复用。
关键点:
AddHttpClient注册时传入的配置(如 BaseAddress、Timeout、DefaultRequestHeaders)只影响新生成的 client 实例,不影响 handler handler 级别配置(如
MaxConnectionsPerServer)必须通过
ConfigurePrimaryHttpMessageHandler或自定义
IHttpMessageHandlerBuilderFilter设置 命名客户端(如
services.AddHttpClient("github", c => c.BaseAddress = new Uri("https://api.github.com/")))适合多服务场景,类型化客户端更利于单元测试
类型化客户端(Typed Client)怎么写才对?
类型化客户端本质是一个普通类,构造函数接收
IHttpClientFactory或直接注入
HttpClient(推荐后者,更简洁)。它不是单例,每次解析都新建,但内部的
HttpClient由工厂供应,生命周期解耦。
示例:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("myapp/1.0");
}
public async Task<string> GetLatestRelease(string repo)
{
var response = await _httpClient.GetAsync($"/repos/{repo}/releases/latest");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
注册方式:
services.AddHttpClient<GitHubService>(client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.Timeout = TimeSpan.FromSeconds(10);
});
注意:
AddHttpClient<t></t>注册的是服务类型,不是
HttpClient;
T构造函数中注入的
HttpClient已被工厂包装,无需再处理重试或超时逻辑(这些应放在 factory 配置或 Polly 策略里)。
超时、重试、认证这些怎么加?
超时建议设在
HttpClient实例级别(通过
AddHttpClient配置),而不是在每次请求时用
CancellationToken控制——后者只中断当前请求,不释放连接;前者能触发 handler 层连接清理。
重试和断路器必须用
IHttpClientBuilder.AddPolicyHandler(基于 Polly),例如:
services.AddHttpClient<GitHubService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler(Policy.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
认证头推荐用
AddHttpMessageHandler注入自定义
DelegatingHandler,避免每次手动设置
DefaultRequestHeaders: Token 过期需刷新?不要在 handler 里同步调用
GetAccessTokenAsync,改用
IAsyncAuthorizationHeaderProvider模式或配合
ITokenProvider+
RefreshTokenHandlerBasic Auth 明文密码?把凭据存在
IConfiguration或
ISecretsManager,handler 中读取后编码 Bearer Token 需动态更新?handler 中检查
DateTimeOffset.Now ,过期则异步刷新并更新 <code>Authorization头
handler 的
SendAsync是同步入口,所有 await 必须显式声明,否则会阻塞连接池线程。
