Search code examples
c#async-awaithttpclientsendasyncihttpclientfactory

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


I am trying to send a post request, but when debugging I am receiving the AggregateException:

"An error occurred while copying content to a stream." with InnerException "Unable to read data from the transfer connection: the connection has been closed. An error occurred while copying content to a stream." - What could this mean?

Without debugging I get the error "Exception in HttpClientHandler - The request was aborted: a secure SSL/TLS channel could not be created...".

I am using IHttpClientFactory and the protocol is set to Tls12 which is correct one for me to use.

Using postman I can successfully send a post request with my client certificate.

I would appreciate it if anyone could point out flaws in my code and any possible ideas in order to correct the issue. Let me know if I need to be more clear or post more code. Thanks!

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

Call stack with debugging

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)

Call stack without debugging

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.

Network Logs

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

UPDATE: After disabling "Just My Code" and enabling all "Exception Settings", an System.Security.Authentication.AuthenticationException: The message received was unexpected or badly formatted". The general solution to this problem is check that the correct/latest TLS version is used and to check the cipher suites. Based off what I captured in Wireshark I see no TLS and Cipher issue. TLS1.2 and TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 are used.

enter image description here


Solution

  • The solution to my problem lied in my Windows Registry Editor.

    What brought me to this was using the curl command. I provided my cert and key in pem format along with the full url of the post request.

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

    A part of the error message I received was:

    * schannel: disabled automatic use of client certificate
    

    A quick Google turned up the following documentation about SChannel configuration: https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings?tabs=diffie-hellman#messaging--fragment-parsing

    Registry path: HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Messaging

    To specify a maximum allowed size of fragmented TLS handshake messages that the TLS client will accept, create a MessageLimitClient entry. After you have created the entry, change the DWORD value to the desired bit length. If not configured, the default value will be 0x8000 bytes.

    Once I added a MessageLimitClient, I was able to receive a response from my Post request.