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

.Net Framework HttpClient SendAsync连接及SSL/TLS异常求助

客户端证书+TLS1.2请求失败:SSL/TLS通道创建失败及流复制错误

尝试发送POST请求时,调试阶段抛出AggregateException,提示“将内容复制到流时出错”,内部异常为“无法从传输连接读取数据:连接已关闭。将内容复制到流时出错”;未调试时则收到“HttpClientHandler异常 - 请求已中止:无法创建安全SSL/TLS通道...”错误。

已确认:

  • 使用IHttpClientFactory,且已将协议设置为Tls12(符合服务端要求)
  • Postman携带相同客户端证书可成功发送请求

错误信息

调试时调用堆栈

System.Net.Http.HttpRequestException: 'Fehler beim Kopieren von Inhalt in einen Stream.'
Inner Exception: IOException: Von der Übertragungsverbindung können keine Daten gelesen werden: Die Verbindung wurde geschlossen.
StackTrace: at System.Threading.Tasks.TaskFactory1.FromAsyncCoreLogic(IAsyncResult iar, Func2 endFunction, Action1 endAction, Task1 promise, Boolean requiresSynchronization)

未调试时调用堆栈

12.01.2023 14:25:04:5738|INFO|Service|23192|0| * ITAD-199497 * Exception Caught!
12.01.2023 14:25:04:5749|ERROR|Service|23192|Message :{0} | * ITAD-199497 * Fehler beim Senden der Anforderung.
12.01.2023 14:25:04:5749|INFO|TXMS.PaymentCapture|18768|0| * ITAD-199497 * Caught aggregate exception-Task.Wait behavior
12.01.2023 14:25:04:5749|ERROR|TXMS.PaymentCapture|18768|0| * ITAD-199497 * PayID: 95052fe0a86246569c23976899a20ced. Die Anfrage wurde abgebrochen: Es konnte kein geschützter SSL/TLS-Kanal erstellt werden..
12.01.2023 14:25:04:5749|VERBOSE|TXMS.PaymentCapture|18768|0| * ITAD-199497 * bei System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context)
bei System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)
12.01.2023 14:25:04:5758|ERROR|ProcessList|18768|0| * ITAD-199497 * Common.TXMSException: Unhandled exception when calling the API.

网络日志

System.Net.Http Error: 0 : [24128] HttpClient#56105527::SendAsync() - Fehler beim Senden von HttpRequestMessage#10319855. System.Net.Http.HttpRequestException: Fehler beim Kopieren von Inhalt in einen Stream. ---> System.IO.IOException: Von der Übertragungsverbindung können keine Daten gelesen werden: Die Verbindung wurde geschlossen.
bei System.Net.ConnectStream.EndWrite(IAsyncResult asyncResult)
bei System.Threading.Tasks.TaskFactory1.FromAsyncCoreLogic(IAsyncResult iar, Func2 endFunction, Action1 endAction, Task1 promise, Boolean requiresSynchronization)
--- Ende der internen Ausnahmestapelüberwachung ---
bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
bei Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.d__5.MoveNext()
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
bei Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.d__5.MoveNext()
ProcessId=4124
DateTime=2023-01-12T13:40:20.6476208Z

更新后的异常

禁用"仅我的代码"并启用所有"异常设置"后,抛出System.Security.Authentication.AuthenticationException:“收到的消息意外或格式错误”。Wireshark捕获显示已使用TLS1.2和TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256密码套件,符合服务端要求。


相关代码

Payment.cs

private async Task<HttpResponseMessage> CreatePurchase()
{
    PurchaseService purchaseService = new PurchaseService(context, repository, pm.CategoryCode);
    var httpResponseMessage = await purchaseService.CreatePurchaseRequest(context);
    return httpResponseMessage;
}

public async Task<HttpResponseMessage> CreatePurchaseRequest(Context context)
{
    ServicePointManager.Expect100Continue = true;
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

    endPoint = repository[$"PurchaseService/Endpoints/CreateSettlementOnlyPurchase"];
    // Configure httpClient with IHttpClientFactory
    HttpClientFactoryProvider httpClientFactoryProvider = new HttpClientFactoryProvider(context.Merchant.Test);

    // Get client from ServiceCollection
    var httpClient = httpClientFactoryProvider.GetClient("Purchase");
                   
    // Create settlePurchaseRequest object
    SettlePurchaseRequest settlePurchaseRequest = CreateSettlePurchase(context);

    // Serialize object into JSON
    var purchaseRequest = settlePurchaseRequest.ToJson();

    // Create digest
    var payloadDigest = purchaseRequest != null ? Digest(purchaseRequest) : null;
            
    Dictionary<string, string> signHeaderInfo = CreateSignHeadersInfo(HeaderDateName, now, "POST", targetURL + endPoint, payloadDigest);

    // Wrap JSON inside a StringContent object
    var content = new StringContent(purchaseRequest, Encoding.UTF8, "application/json");

    // Post to the endpoint
    var requestMessage = new HttpRequestMessage(HttpMethod.Post, endPoint);
    requestMessage.Content = content;
    requestMessage.Headers.Add(HeaderDateName, now);
    var guid = Guid.NewGuid().ToString();
    requestMessage.Headers.Add("X-Request-ID", guid);
    var signature = CreateSignature(signHeaderInfo);
    requestMessage.Headers.Add("Signature", signature);
    requestMessage.Headers.Add("Digest", payloadDigest);

    using (HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead))
    {
        // Process response
        httpResponseMessage.EnsureSuccessStatusCode();
        var jsonString = httpResponseMessage.Content.ReadAsStringAsync().Result;

        if (httpResponseMessage.IsSuccessStatusCode)
        {
            return httpResponseMessage;
        }
        else
        {
           return httpResponseMessage; // Todo: Refactor 
        }
    }
}

private IHttpClientFactory HttpClientFactory()
{
    if (_httpClientFactory != null)
    {
        return _httpClientFactory;
    }

    #region DI Service
    var serviceCollection = new ServiceCollection();

    #region Create Settlement Only Purchase
    serviceCollection.AddHttpClient("Purchase", client =>
    {
        client.BaseAddress = new Uri(ApiURL);
        client.DefaultRequestHeaders.Add("Api-Key", PurchaseApiKey);
        client.DefaultRequestHeaders.Add("Accept", "*/*");
        client.DefaultRequestHeaders.Add("Connection", "Keep-Alive");
        client.DefaultRequestHeaders.Add("Keep-Alive", "3600");
        client.DefaultRequestHeaders.Add("Host", hostName);
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        handler.ClientCertificates.Add(GetCertificateBySerialNumber());
        handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
        //handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
        return handler;
    });
    #endregion

    var serviceProvider = serviceCollection.BuildServiceProvider(); 
    _httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();

    return _httpClientFactory;
    #endregion
}

public HttpClient GetClient(string clientName)
{
    return HttpClientFactory().CreateClient(clientName);
}

分析与修正思路

1. 客户端证书加载问题

  • 检查GetCertificateBySerialNumber()是否正确加载证书的私钥:Postman能成功说明证书本身有效,代码中可能仅加载了公钥证书,未包含私钥。可通过Certificate.HasPrivateKey验证。
  • 确认证书存储位置,且程序运行账号拥有读取证书私钥的权限(区分本地机器存储/当前用户存储)。

2. HttpClientFactory的不当使用

  • 当前代码每次请求都实例化HttpClientFactoryProvider并重建ServiceCollection,会创建大量重复HttpClient实例,破坏连接池机制,引发连接异常。
  • 修正:在应用启动时将IHttpClientFactory注册到DI容器(如Startup/Program.cs),通过构造函数注入使用,而非每次请求手动构建。

3. ServicePointManager设置时机

  • ServicePointManager.SecurityProtocol应在应用启动时设置一次,而非每次请求重复设置,多线程环境下多次设置可能引发冲突。

4. 同步调用异步方法的风险

  • CreatePurchaseRequest中使用ReadAsStringAsync().Result同步等待异步任务,可能引发死锁或上下文冲突,导致连接异常。改为await httpResponseMessage.Content.ReadAsStringAsync()。

5. 请求头一致性检查

  • 对比Postman和代码的所有请求头:
    • 移除手动添加的Host头,HttpClient会自动根据BaseAddress生成,手动设置可能导致格式错误。
    • 验证Digest和Signature的生成逻辑是否与服务端规则匹配(哈希算法、签名格式)。
    • 确认HeaderDateName对应的日期格式符合服务端要求(如RFC 1123格式)。

6. TLS握手额外配置

  • 调试时可临时设置handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true跳过证书链验证,确认是否为证书信任问题(生产环境禁用)。
  • 尝试设置ServicePointManager.Expect100Continue = false,部分服务端不支持该特性可能导致握手失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 14:45:31