Search code examples
c#.netdotnet-httpclient

HttpClient got error with EstablishSslConnectionAsync at SendAsync method


I have ApiAdapter with using HttpClient to send HTTP requests to my server. to avoid create to many HttpClient, I am using DependenceInjection with singleton mode for "ApiAdapter" class. it works well when I call only one by one HTTP request

I want to use Parallel.ForEachAsync to reduce performance time because I want to create 1000 people through ApiAdapter. It throw exception with below message error after it created ~ 30 - 100 person with message:

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.ArgumentNullException: Value cannot be null. (Parameter 'value') at System.Collections.CollectionBase.OnValidate(Object value) at System.Security.Cryptography.X509Certificates.X509CertificateCollection.OnValidate(Object value) at System.Collections.CollectionBase.System.Collections.IList.Add(Object value) at System.Security.Cryptography.X509Certificates.X509CertificateCollection.AddRange(X509CertificateCollection value) at System.Net.Security.CertificateHelper.GetEligibleClientCertificate(X509CertificateCollection candidateCerts) at System.Net.Http.HttpClientHandler.<set_ClientCertificateOptions>b__57_0(Object sender, String targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, String[] acceptableIssuers) at System.Net.Security.SslStream.UserCertSelectionCallbackWrapper(String targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, String[] acceptableIssuers) at System.Net.Security.SecureChannel.AcquireClientCredentials(Byte[]& thumbPrint) at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan1 inputBuffer, Byte[]& output) at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan1 incomingBuffer) at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm) at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request) at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, Cancellatio nToken originalCancellationToken) at ---------.ApiAdapter.PostRequestAsync

Here is my code 
public async Task RequestPerson()
{
    var threadRequestPins = Enumerable.Range(0, listSize).ToList();
    await Parallel.ForEachAsync(datasToProcess, async (_, token) =>{
        var fakfurTestApiService = LoadTestKernel.Kernel.Get<IFakfurApiService>();
        var personCreated = await fakfurTestApiService.CallPostRequestAsync(<Url path>, <dataBody>);
        //CallPostRequestAsync will call ApiAdapter with PostRequestAsync() method below
    });
}

//constructor
public ApiAdapter()
{
    _cookieContainer = new CookieContainer();
    _httpClientHandler = new HttpClientHandler
    {
        CookieContainer = _cookieContainer
    };
    _httpClient = new HttpClient(_httpClientHandler);
}

private void AddCertificates()
{
    if (_certificate2Collection != null )
    {
        _httpClientHandler.ClientCertificates.AddRange(_certificate2Collection);
    }
}

public async Task<TResult> PostRequestAsync<TResult, TBodyRequest>(string testApiRelativeUrl, TBodyRequest bodyRequest,
            CancellationToken cancellationToken = default) where TResult : class
{
    var uriBuilder = BuildRequestUri(testApiRelativeUrl);
    var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri);
    AddCertificate(request);
    request.Content = new StringContent(_jsonSerializer.SerializeObject(bodyRequest), Encoding.UTF8,;
    var response = await _httpClient.SendAsync(request, cancellationToken);
    return await ExtractBodyFromResponseAsync<TResult>(response);
}


Solution

  • It's difficult to guage what's actually going on here as the code you have provided is missing alot.

    However, as per this issue > https://github.com/dotnet/runtime/issues/52779 You cannot add certificates whilst building the request or when the HttpClient is in use.

    You should use the SocketsHttpHandler class instead of HttpClientHandler.

    You can assign your certificate handler delegate in your constructor.

        private readonly CookieContainer _cookieContainer;
        private readonly SocketsHttpHandler _socketsHttpHandler;
        private readonly HttpClient _httpClient;
    
        //constructor
        public ApiAdapter()
        {
            _cookieContainer = new CookieContainer();
            _socketsHttpHandler = new SocketsHttpHandler
            {
                CookieContainer = _cookieContainer,
                SslOptions = new System.Net.Security.SslClientAuthenticationOptions()
                {
                    LocalCertificateSelectionCallback = (object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate? remoteCertificate, string[] acceptableIssuers) =>
                    {
                        return <your certificate here>;
    
                    }
                }
            };
            _httpClient = new HttpClient(_socketsHttpHandler);
        }
    

    Sidenote: If you're using .NET Core you should really look into using Dependency Injection to reference your services such as the HttpClient.