Home > Back-end >  .NET 6 Port exhaustion when using HttpClient in factory class
.NET 6 Port exhaustion when using HttpClient in factory class

Time:08-16

I've been experiencing some port exhaustion recently, and I recently stumbled across this video by Nick Chapsas where he talks about how misusing HttpClient can lead to some of the same issues that I've been seeing.

I use the HttpClientFactory when configuring the services like how he recommends, but I'm unsure if sharing this one client among different classes could be coming back to bite me.

I can't share the exact code, but here's a recreation of how it's being used in the application.

Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IWebServiceFactory, WebServiceFactory>();
builder.Services.AddHttpClient<IWebServiceFactory, WebServiceFactory>();

var app = builder.Build();

builder.Services.AddControllersWithViews();
... // omitted for berevity

WebServiceFactory.cs

using Test.Services.WebServices;

namespace Test.Services;
public class WebServiceFactory : IWebServiceFactory
{
    private readonly HttpClient _httpClient;

    public WebServiceFactory(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public IWebService GetWebService(WebServiceType webServiceType)
    {
        return webServiceType switch
        {
            WebServiceType.WEBSERVICE_TYPE_A => new WebServiceA(_httpClient),
            WebServiceType.WEBSERVICE_TYPE_B => new WebServiceB(_httpClient),
            WebServiceType.WEBSERVICE_TYPE_C => new WebServiceC(_httpClient),
            _ => throw new ArgumentException("Web service not implemented for type")
        };
    }
}

WebServiceA.cs (WebService B and C are nearly identical)

namespace Test.Services.WebServices;
public class WebServiceA : IWebService
{
    private readonly int _timeout = 10_000; // in milliseconds
    private readonly HttpClient _httpClient;

    public WebServiceA(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public string GetResponse(string id)
    {
        string uri = $"https://fakesiteA.com?id={id}";

        HttpRequestMessage request = new HttpRequestMessage();
        request.Method = HttpMethod.Get;
        request.RequestUri = new Uri(uri);

        HttpResponseMessage response = SendRequest(request).Result;
        return response.Content.ReadAsStringAsync().Result;
    }

    private async Task<HttpResponseMessage> SendRequest(HttpRequestMessage requestMessage)
    {
        using var cts = new CancellationTokenSource();
        cts.CancelAfter(TimeSpan.FromMilliseconds(_timeout));
        var response = await _httpClient.SendAsync(requestMessage, cts.Token);
        return response;
    }
}

WebServiceFactory accepts the HttpClient, and passes it through to WebServiceA, WebServiceB, or WebServiceC depending on the value of WebServiceType.

Was wondering if this implementation had any drawbacks, and could maybe be leading to the port exhaustion issue I've been encountering.

Any thoughts?

Edit: should've mentioned that the calling code will then call GetResponse(id) on the webservice after it's returned. Maybe it was obvious, but figured I should be more explicit.

CodePudding user response:

You could inject IHttpClientFactory instead of HttpClient which would allow you to create new clients for each service. However, I would suggest you inject IServiceProvider and use that instead of manually doing new WebServiceA(...).

So something like this:

public class WebServiceFactory : IWebServiceFactory
{
    private readonly IServiceProvider _provider;

    public WebServiceFactory(IServiceProvider provider)
    {
        _provider= provider;
    }

    public IWebService GetWebService(WebServiceType webServiceType)
    {
        return webServiceType switch
        {
            WebServiceType.WEBSERVICE_TYPE_A => _provider.GetRequiredService<WebServiceA>(),
            WebServiceType.WEBSERVICE_TYPE_B => _provider.GetRequiredService<WebServiceB>(),
            WebServiceType.WEBSERVICE_TYPE_C => _provider.GetRequiredService<WebServiceC>(),
            _ => throw new ArgumentException("Web service not implemented for type")
        };
    }
}

And set up your DI like this:

builder.Services.AddScoped<IWebServiceFactory, WebServiceFactory>();
builder.Services.AddScoped<WebServiceA>();
builder.Services.AddScoped<WebServiceB>();
builder.Services.AddScoped<WebServiceC>();

The benefit of this is each of your services can be pulled from DI so will get their own HttpClient and anything else they may need.

Side note: Calling AddHttpClient<T> overrides a call to AddScoped<T> and redefines the service as transient.

CodePudding user response:

I think your problem is here:

builder.Services.AddScoped<IWebServiceFactory, WebServiceFactory>();

Factories are generally Singletons. You are basically creating a new factory every time a message arrives, which causes the original problem.

Try:

builder.Services.AddSingleton<IWebServiceFactory, WebServiceFactory>();
  • Related