.NetStandard2.0使用HttpClient/IHttpClientFactory时遇响应提前终止错误
问题描述
我有一个.NetStandard2.0类库,被.Net Framework 4.7.2 Web API以DLL形式引用。最初使用静态HttpClient实现时,偶发最内层异常**"The Response ended prematurely"**,平均每小时出现50次,并非所有请求都会触发。
静态HttpClient实现代码
public class Helper { private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler() { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate }) { Timeout = TimeSpan.FromSeconds(15) }; public async Task<string> ProviderCallAsync(string url, string Request, string AccessToken, HttpMethod HttpMethod) { string ProviderResponse = ""; try { if (HttpMethod == HttpMethod.Get) { url = url + Request; } HttpRequestMessage request = new HttpRequestMessage(HttpMethod, url); if (HttpMethod != HttpMethod.Get) { request.Content = new StringContent(Request, Encoding.UTF8, "application/json-patch+json"); } if (!string.IsNullOrWhiteSpace(AccessToken)) { request.Headers.Add("Authorization", "Bearer " + AccessToken); } request.Headers.Add("Accept-Encoding", "gzip, deflate"); System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls; HttpResponseMessage response = await _httpClient.SendAsync(request); ProviderResponse = await response?.Content?.ReadAsStringAsync(); } catch (Exception ex) { } return ProviderResponse; } }
之后尝试改用IHttpClientFactory实现(因类库无Startup.cs,按如下方式实现),但仍出现相同异常。
IHttpClientFactory实现代码
public class Helper { private static readonly object Instancelock = new object(); private readonly IHttpClientFactory _httpClientFactory = GetHttpClientFactory(); private static IServiceProvider serviceProvider { get; set; } public static void Initialize() { if (serviceProvider == null) { lock (Instancelock) { if (serviceProvider == null) { var services = new ServiceCollection(); services.AddHttpClient("HttpClient", client => { client.Timeout = TimeSpan.FromSeconds(15); }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate, SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls }).SetHandlerLifetime(TimeSpan.FromMinutes(1)); serviceProvider = services.BuildServiceProvider(); } } } } public static IHttpClientFactory GetHttpClientFactory() { if (serviceProvider == null) { Initialize(); } return (IHttpClientFactory)serviceProvider.GetServices<IHttpClientFactory>().First(); } public async Task<string> ProviderCallAsync(string url, string Request, string AccessToken, HttpMethod HttpMethod, [System.Runtime.CompilerServices.CallerMemberName] string CallerMemberName = "", [System.Runtime.CompilerServices.CallerFilePath] string CallerFilePath = "") { string ProviderResponse = ""; try { if (HttpMethod == HttpMethod.Get) { url = url + Request; } HttpRequestMessage request = new HttpRequestMessage(HttpMethod, url); if (HttpMethod != HttpMethod.Get) { request.Content = new StringContent(Request, Encoding.UTF8, "application/json-patch+json"); } if (!string.IsNullOrWhiteSpace(AccessToken)) { request.Headers.Add("Authorization", "Bearer " + AccessToken); } request.Headers.Add("Accept-Encoding", "gzip, deflate"); var httpClient = _httpClientFactory.CreateClient("HttpClient"); using (var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) { ProviderResponse = await response?.Content?.ReadAsStringAsync(); } } catch (Exception ex) { } return ProviderResponse; } }
解决建议
修复全局配置的重复设置问题
ServicePointManager的配置属于全局设置,不需要每次请求都重复赋值,把这部分逻辑移到类的静态构造函数里,避免多线程冲突:static Helper() { System.Net.ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls; // 额外优化连接池参数,减少连接闲置超时问题 ServicePointManager.DefaultConnectionLimit = 100; ServicePointManager.MaxServicePointIdleTime = 10000; }排查压缩响应导致的异常
"The Response ended prematurely"经常和Gzip/Deflate压缩的不完整响应有关,可以尝试两种方案:- 移除手动添加的
Accept-Encoding请求头,因为HttpClientHandler的AutomaticDecompression已经会自动处理压缩,手动添加可能导致重复处理逻辑混乱 - 确保响应流被完全读取,替换
ReadAsStringAsync为更严谨的流读取方式:using (var stream = await response.Content.ReadAsStreamAsync()) using (var reader = new StreamReader(stream)) { ProviderResponse = await reader.ReadToEndAsync(); }
- 移除手动添加的
优化IHttpClientFactory实现
- 把
GetServices<IHttpClientFactory>().First()改成GetRequiredService<IHttpClientFactory>(),更高效且符合常规用法 - 不要在类构造函数里直接初始化
_httpClientFactory,建议在ProviderCallAsync里延迟获取,或者确保Initialize方法在类使用前被调用 SetHandlerLifetime(TimeSpan.FromMinutes(1))设置过短,会导致频繁创建Handler,建议改为5-10分钟,或者使用默认值
- 把
添加异常日志与重试机制
空catch块完全隐藏了异常细节,至少要记录日志方便排查,同时针对网络异常添加重试:catch (Exception ex) { // 这里替换为你实际使用的日志框架,比如log4net、NLog // Log.Error(ex, $"请求{url}失败,调用方:{CallerMemberName}"); // 针对网络类异常做重试,最多重试2次 if (ex is HttpRequestException || ex is IOException) { await Task.Delay(100); return await ProviderCallAsync(url, Request, AccessToken, HttpMethod, CallerMemberName, CallerFilePath); } }检查目标服务稳定性
该异常也可能是目标服务提前关闭连接导致的,建议和目标服务维护方确认:是否存在服务端超时、连接池配置不合理,或者是否有流量限制强制断开连接的情况。
内容的提问来源于stack exchange,提问作者Nishad
相关产品推荐
相关产品推荐

