You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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;
        }
    }
解决建议
  1. 修复全局配置的重复设置问题
    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;
    }
    
  2. 排查压缩响应导致的异常
    "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();
      }
      
  3. 优化IHttpClientFactory实现

    • 把GetServices<IHttpClientFactory>().First()改成GetRequiredService<IHttpClientFactory>(),更高效且符合常规用法
    • 不要在类构造函数里直接初始化_httpClientFactory,建议在ProviderCallAsync里延迟获取,或者确保Initialize方法在类使用前被调用
    • SetHandlerLifetime(TimeSpan.FromMinutes(1))设置过短,会导致频繁创建Handler,建议改为5-10分钟,或者使用默认值
  4. 添加异常日志与重试机制
    空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);
        }
    }
    
  5. 检查目标服务稳定性
    该异常也可能是目标服务提前关闭连接导致的,建议和目标服务维护方确认:是否存在服务端超时、连接池配置不合理,或者是否有流量限制强制断开连接的情况。

内容的提问来源于stack exchange,提问作者Nishad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 11:25:01