Search code examples
azure-functionsservicebus

SocketException when calling external api


I'm calling an API of mine from an Azure Function ServiceBusTrigger using a custom class "ApiProvider"

public class ApiProvider
{
    private readonly string _backendUrl;
    private static HttpClient _client;
    private readonly TraceWriter _log;

    public ApiProvider(TraceWriter log)
    {
        _pamUrl = Environment.GetEnvironmentVariable("ApiUrl");
        _log = log;
        _client = CreateHttpClient();
    }
}

I'm calling it for each message sent in the ServiceBus queue, so let's say there are 1500 messages, it's going to call my backed 1500 times.

Some of the calls are successfull but sometimes I have an error in Azure Functions Log without too much information ! But according to the Application Insights:

System.Net.Sockets.SocketException
Exception while executing function: QueueDirectory An error occurred while sending the request. Unable to connect to the remote server Only one usage of each socket address (protocol/network address/port) is normally permitted

I thought it was sufficient to make HttpClient static but it doesn't seem to solve entirely the issue I guess, or am I missing something ?

Environment: Azure Functions Runtime : 1.0.11702.0

EDIT: a little overview of my method CreateHttpClient():

    private static HttpClient CreateHttpClient()
    {
        string thumbprint = Environment.GetEnvironmentVariable("WEBSITE_LOAD_CERTIFICATES");
        if (thumbprint != null)
        {
            _log.LogInformation("Get the certificate of thumbprint : " + thumbprint);

            using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
            {
                certStore.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection certCollection =
                    certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);

                // Get the first cert with the thumbprint
                var certificate = certCollection.OfType<X509Certificate2>().FirstOrDefault();
                if (certificate != null)
                {
                    _log.LogInformation("Certificate has been found.");
                    var handler = new WebRequestHandler();
                    handler.ClientCertificates.Add(certCollection[0]);
                    return new HttpClient(handler);
                }
                _log.LogInformation("Certificate has not been found.");
            }
        }
        return new HttpClient();
    }

Solution

  • You should create HttpClient just once, not for each request:

    private static HttpClient _client = new HttpClient();
    

    or if you need to keep your initialization:

    public class ApiProvider
    {
        private readonly string _backendUrl;
        private static HttpClient _client;
        private readonly TraceWriter _log;
    
        static ApiProvider()
        {
            _client = CreateHttpClient();
        }
    
        public ApiProvider(TraceWriter log)
        {
            _pamUrl = Environment.GetEnvironmentVariable("ApiUrl");
            _log = log;
        }
    }
    

    UPDATE:

    Base on your edit, I would suggest:

    1. Remove _log usage from CreateHttpClient and just throw an exception if you get an issue with certificate loading. This should allow keeping this method static, and also fail fast and visibly if there is an issue with setup.

    2. If you really need a logger in CreateHttpClient, make it non-static and use your original code but call CreateHttpClient only once:

      public ApiProvider(TraceWriter log)
      {
          _pamUrl = Environment.GetEnvironmentVariable("ApiUrl");
          _log = log;
          if (_client == null) _client = CreateHttpClient();
      }
      

      Some race condition is possible, but I don't see big issue with that. Add lock if you want.