.Net Framework - HttpClient SendAsync 错误 - 无法从传输连接读取数据:连接已关闭

.Net Framework - HttpClient SendAsync Error - Unable to read data from the transfer connection: the connection has been closed

提问人:Colin Roe 提问时间:1/11/2023 最后编辑:Colin Roe 更新时间:2/10/2023 访问量:656

问:

我正在尝试发送 post 请求,但在调试时收到 AggregateException:

“将内容复制到流时出错。” “无法从传输连接读取数据:连接已关闭。将内容复制到流时出错。 - 这可能意味着什么?

在没有调试的情况下,我收到错误“HttpClientHandler 中的异常 - 请求已中止:无法创建安全的 SSL/TLS 通道...”。

我正在使用 IHttpClientFactory,协议设置为 Tls12,这是我使用的正确协议。

使用 postman,我可以使用我的客户端证书成功发送 post 请求。

如果有人能指出我的代码中的缺陷和任何可能的想法以纠正问题,我将不胜感激。 如果我需要更清楚或发布更多代码,请告诉我。 谢谢!

/// 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);
}

使用调试调用堆栈

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.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 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.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 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.<SendAsync>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.<SendAsync>d__5.MoveNext()
    ProcessId=4124
    DateTime=2023-01-12T13:40:20.6476208Z

更新:禁用“仅我的代码”并启用所有“例外设置”后,System.Security.Authentication.AuthenticationException:收到的消息意外或格式错误“。此问题的一般解决方案是检查是否使用了正确/最新的 TLS 版本,并检查密码套件。 根据我在 Wireshark 中捕获的内容,我认为没有 TLS 和密码问题。使用 TLS1.2 和 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。

enter image description here

c# 异步等待 httpclient(英语:httpclient) 森达同步 ihttpclientfactory

评论


答:

2赞 Mike Mozhaev 1/11/2023 #1

您正在从语句内部返回一次性对象。所以它在返回之前被处理掉了。using

    using (HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead))
    {
        // ...
        return httpResponseMessage;
    }

您需要从作用域内的响应消息中读取所需的任何内容,或者删除语句并在完全处理后将消息释放到调用代码中。usingusing

评论

0赞 Colin Roe 1/12/2023
无论如何,同样的错误。在不调试代码的情况下,我收到错误“无法创建受保护的 SSL/TLS 通道”。
0赞 Mike Mozhaev 1/12/2023
请提供异常调用堆栈。
0赞 Colin Roe 1/12/2023
我添加了调用堆栈
1赞 Mike Mozhaev 1/13/2023
您能否尝试打开窗口并配置为在抛出异常时(而不是在未处理异常时)中断,然后重现问题?查看地点并检查异常。主要问题是第一个问题发生在哪里。例如,如果服务器上发生异常,那么客户端上也会发生异常。但我们需要第一个。Exception settings
1赞 Mike Mozhaev 1/31/2023
引发异常后,客户端可能会发送重置。找到引发异常的确切位置会很有帮助。要获取它,请取消选中“调试选项”,并将“异常设置”配置为在抛出异常时中断,而不是在未处理异常时中断。此外,您还可以查看届时 WireShark 中的内容。Enable just my code
1赞 WerkmanW 1/11/2023 #2

我理解您的困惑,看到在 HttpResponseMessage 上实现的接口,但我认为您不需要自己处理它。查看此处的类示例用法,您可以看到您可以简单地使用它,并退出该方法,而无需在语句中调用或保留它。你能试试看它是否有效吗?IDisposableDisposeusing

此外,我对您实现该模式的方式感到有些困惑。依赖注入不适合您吗?如果是这样,我建议遵循本文中提到的准则,并将 注入到需要 .IHttpClientFactoryIHttpClientFactoryHttpClient

评论

0赞 Colin Roe 1/11/2023
我已经删除了 using 语句,但问题仍然存在。DI 的正常实现目前对我来说不是一个选择,所以我不得不即兴发挥。谢谢你的建议!
0赞 Colin Roe 2/10/2023 #3

我问题的解决方案在于我的 Windows 注册表编辑器。

让我想到这一点的是使用 curl 命令。我提供了 pem 格式的证书和密钥以及帖子请求的完整 URL。

curl  --verbose --cert fullchain.pem --key privKey.pem {RequestUri}

我收到的错误消息的一部分是:

* schannel: disabled automatic use of client certificate

快速的谷歌找到了以下关于 SChannel 配置的文档: https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings?tabs=diffie-hellman#messaging--fragment-parsing

注册表路径:HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Messaging

若要指定 TLS 客户端将接受的分段 TLS 握手消息的最大允许大小,请创建一个 MessageLimitClient 条目。创建条目后,将 DWORD 值更改为所需的位长度。如果未配置,则默认值将为 0x8000 字节。

添加 MessageLimitClient 后,我能够收到来自我的 Post 请求的响应。