从Web应用调用外部API时,如何处理各类HttpRequestException异常?
解决.NET HttpClient调用外部API时的连接超时(10060)与连接拒绝(10061)异常
一、先做基础网络排查
- 验证目标API可用性:在部署应用的服务器上,用
curl或Postman持续测试目标URL,确认是否是对方服务间歇性故障、网络链路抖动导致的问题。 - 检查网络权限:
- 确认服务器防火墙/安全组允许出站访问目标API的端口(比如HTTPS的443)。
- 核实目标API服务器是否将当前应用服务器的IP加入白名单,10061错误常因IP被拒绝访问导致。
二、优化HttpClient配置
1. 细粒度超时与连接池配置
默认的Timeout是全局超时,可通过SocketsHttpHandler拆分连接超时、读取超时,同时优化连接池避免复用失效连接:
services.AddHttpClient("LoginClient", client => { client.BaseAddress = new Uri(settings.LoginUrl); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.Timeout = TimeSpan.FromSeconds(settings.APITimeoutInSeconds); // 全局请求超时 }).ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { ConnectTimeout = TimeSpan.FromSeconds(10), // 单独设置连接阶段超时 PooledConnectionLifetime = TimeSpan.FromMinutes(5), // 定期更换连接池中的连接,避免DNS缓存过期 PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2), // 空闲连接超时回收 MaxConnectionsPerServer = 100 // 根据并发量调整,避免连接耗尽 });
2. 移除不必要的using包裹
HttpClientFactory创建的HttpClient无需手动Dispose,Factory会管理底层Handler的生命周期。滥用using会破坏连接池复用,反而引发连接问题:
// 正确写法:不要用using包裹HttpClient var client = _httpClientFactory.CreateClient("LoginClient"); var response = await client.PostAsync(requestUri, content); try { if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync(); res = JsonConvert.DeserializeObject<TokenResponseModel>(responseContent); } else { apiLog.Response = response; } } finally { await response.Content.DisposeAsync(); // 仅释放响应内容 }
三、添加重试机制应对临时故障
针对偶尔的网络波动或服务不可用,用Polly库实现指数退避重试(仅适合幂等请求,POST请求需确认接口支持重试):
- 先安装NuGet包:
Microsoft.Extensions.Http.Polly、Polly - 配置重试策略:
services.AddHttpClient("LoginClient", client => { // 原有客户端配置 }).AddPolicyHandler(Policy .Handle<HttpRequestException>() .Or<SocketException>(ex => ex.ErrorCode is 10060 or 10061) // 针对目标异常重试 .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))) // 指数退避:2s、4s、8s后重试 );
四、增强日志与监控
- 记录详细异常信息:捕获异常时记录Socket错误码、请求时间、目标URL,方便定位问题根源:
try { var client = _httpClientFactory.CreateClient("LoginClient"); var response = await client.PostAsync(requestUri, content); // 原有业务逻辑 } catch (HttpRequestException ex) when (ex.InnerException is SocketException socketEx) { apiLog.ExceptionMessage = ex.Message; apiLog.SocketErrorCode = socketEx.ErrorCode; apiLog.TargetUrl = settings.LoginUrl + requestUri; apiLog.RequestTime = DateTime.UtcNow; // 写入日志系统 }
- 监控API可用性:用Application Insights或Prometheus+Grafana监控请求成功率、响应时间,及时发现对方服务的异常波动。
五、解决DNS缓存过期问题
如果目标API的IP地址会变更,可缩短DNS缓存刷新时间:
new SocketsHttpHandler { DnsRefreshTimeout = TimeSpan.FromMinutes(1), // 每分钟刷新DNS缓存 // 其他配置 }
内容的提问来源于stack exchange,提问作者Mahesh
相关产品推荐
相关产品推荐

