Search code examples
c#azureazure-web-app-servicehttpclientasp.net-core-2.0

How do I create HttpClient on ASP.Net Core 2.0 to prevent SNAT port exhaustion on Azure WebApps


To reuse HttpClient, I created a utility class like this on ASP.Net Core 2.0.

    public class HttpClientUtility
    {
        private static HttpClientUtility _singleInstance = new HttpClientUtility();
        public static HttpClientUtility GetInstance()
        {
            return _singleInstance;
        }
        private HttpClientUtility()
        {
        }

        private static readonly ConcurrentDictionary<string, HttpClient> _HttpClientDict = new ConcurrentDictionary<string, HttpClient>();

        private HttpClient GetHttpClient(
            Uri uri)
        {
            return _HttpClientDict.GetOrAdd(GetHostCacheKeyFromUri(uri),
                (n) =>
                {
                    return new HttpClient();
                });
        }

        private static string GetHostCacheKeyFromUri(
            Uri uri)
        {
            return $"{uri.Scheme}://{uri.DnsSafeHost}:{uri.Port}";
        }

        private async Task<HttpRequestMessage> CreateRequestMessage(
            Uri requestUri,
            HttpMethod method,
            IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers,
            HttpContent content,
            bool contentCopy = true
            )
        {
            var requestMessage = new HttpRequestMessage(method, requestUri);

            if (headers != null)
            {
                foreach (var header in headers)
                {
                    requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
                }
            }

            if (content != null)
            {
                if (contentCopy)
                {
                    var contentStream = new MemoryStream();
                    await content.CopyToAsync(contentStream);
                    contentStream.Position = 0;
                    requestMessage.Content = new StreamContent(contentStream);

                    if (requestMessage.Content.Headers != null)
                    {
                        foreach (var header in content.Headers)
                        {
                            requestMessage.Content.Headers.Add(header.Key, header.Value);
                        }
                    }
                }
                else
                {
                    requestMessage.Content = content;
                }
            }

            return requestMessage;
        }

        public async Task<HttpResponseMessage> SendRequestWithRetry(
            Uri requestUri,
            HttpMethod method,
            HttpContent content,
            IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers,
            Func<HttpResponseMessage, bool> needRetry)
        {
            HttpResponseMessage response = null;

            var httpClient = GetHttpClient(requestUri);

            for (int i = 0; i <= MaxRetryCount; i++)
            {
                // Create request message for retry
                var isPossblRetry = needRetry != null && MaxRetryCount > 0;
                var request = await CreateRequestMessage(requestUri, method, headers, content, isPossblRetry).ConfigureAwait(false);

                response = await httpClient.SendAsync(request).ConfigureAwait(false);

                if (response.IsSuccessStatusCode || needRetry == null || !needRetry(response))
                {
                    break;
                }
            }

            return response;
        }
    }

With this code, I think a single HttpClient instance would be used for the same request url.

But, Diagnose and solve problems blade on Azure Portal says SNAT PORT Exhaustion occured.

Is this code cause this problem when many concurrent requests occured?

If so, how do I create HttpClient on ASP.Net Core 2.0 (without HttpClientFactory).

[Environment]

  • ASP.Net Core 2.0
  • Azure WebApps

Solution

  • Thanks for all answers and comments. But I got answer from MS. They says that if there are many requests over 128 for the same url, [SNAT Port Exhaustion] will occur on Azure WebApps even if I use HttpClientFactory or a static HttpClient. The only solution seems to be to scale out WebApps or use ASE.

    So I would do the suggested coding, and scale out.