IHttpClientFactory: Otimizando o uso do HttpClient em .NET

Ao desenvolver aplicações .NET, é comum que você precise fazer requisições HTTP para consumir APIs externas. Embora o HttpClient seja uma ferramenta bastante robusta para esse propósito, ele também apresenta desafios como esgotamento de portas e comportamento inadequado em relação ao DNS quando mal utilizado. Neste artigo, vamos explorar maneiras de configurar e utilizar o HttpClient, focando nas práticas recomendadas para evitar problemas de performance e escalabilidade.

Problema comum com HttpClient

A abordagem mais simples para usar o HttpClient é criar uma nova instância sempre que for fazer uma requisição.

				
					var client = new HttpClient();
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
var response = await client.GetAsync("/posts");

				
			

Embora essa solução funcione, ela cria novos problemas. O ideal é que as instâncias do HttpClient sejam reutilizadas durante a vida útil da aplicação, pois criar novas instâncias constantemente pode causar esgotamento de portas.

Esgotamento de portas (Socket Exhaustion)

Cada vez que você instancia a classe HttpClient e faz uma requisição, é criada uma nova conexão de socket. No entanto, essas conexões não são imediatamente fechadas após a requisição terminar. Em vez disso, elas entram em um estado conhecido como TIME_WAIT, em que a conexão aguarda por um tempo antes de ser fechada completamente para garantir que pacotes de dados atrasados não sejam processados incorretamente.

Como resultado, se você instanciar muitos “HttpClient” repetidamente sem reutilizá-los, o sistema acabará com uma quantidade enorme de conexões de rede pendentes nesse estado. Isso pode levar ao esgotamento de portas, quando o sistema não consegue abrir novas conexões de rede porque todas as portas disponíveis estão ocupadas pelas conexões abertas anteriormente.

Uma prática recomendada ao usar HttpClient é reutilizar instâncias ao longo da vida útil da aplicação. Isso permite que as conexões subjacentes sejam mantidas abertas e reutilizadas para várias requisições, economizando tempo e evitando o problema de esgotamento de portas. No entanto, simplesmente reutilizar o HttpClient diretamente pode ter outros problemas, como manter conexões abertas indefinidamente, o que pode causar falhas se o servidor de destino mudar o endereço IP ou se houver alterações no DNS.

Para gerenciar esse problema de forma eficiente, o HttpClient foi aprimorado nas versões mais recentes do .NET com o uso do HttpClientFactory que permite a criação e configuração de instâncias do HttpClient de forma eficiente, sem a necessidade de gerenciar manualmente o tempo de vida das instâncias.

Utilizando IHttpClientFactory

O primeiro passo para utilizar o IHttpClientFactory é registrar o serviço no contêiner de injeção de dependências. Isso normalmente é feito no arquivo “Program.cs”, onde você configura os serviços da sua aplicação:

				
					builder.Services.AddHttpClient();
				
			

Ao contrário da abordagem tradicional, em que uma nova instância de “HttpClient” é criada diretamente em cada requisição, com o “IHttpClientFactory” você pode solicitar um cliente HTTP sempre que precisar, e a fábrica gerenciará a criação e o descarte dessas instâncias de maneira otimizada.  No exemplo abaixo, injetamos essa interface no construtor da classe service e a utilizamos a seguir, em um método que realiza uma requisição HTTP.

				
					private readonly IHttpClientFactory _httpClientFactory;

    public BlogService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<string> BuscarPostsAsync()
    {
        var client = _httpClientFactory.CreateClient();
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
        
        var response = await client.GetAsync("/posts");
        return await response.Content.ReadAsStringAsync();
    }

				
			

Observe que agora, ao invés de utilizar o operador “new”, utilizamos o método “CreateClient()” para obter uma nova instância de HttpClient.

Usando clientes nomeados ou tipados

Uma outra possibilidade de configurar clientes HTTP é usar clientes nomeados e clientes tipados. 

				
					public class BlogService
{
    private readonly HttpClient _httpClient;

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

    public async Task<string> BuscarPostsAsync()
    {
        var response = await _httpClient.GetAsync("/posts");
        return await response.Content.ReadAsStringAsync();
    }
}

				
			

Neste caso, a classe “BlogService” recebe uma instância do “HttpClient” configurado automaticamente. O método “ListarPostsAsync” utiliza a URL base que já foi definida na configuração. Assim, não é necessário passar um nome para criar uma nova instância do cliente, resultando em um código mais limpo e organizado.

Observação: Tanto os clientes nomeados quanto os tipados devem ser configurados no “Program.cs”, após as outras injeções de dependências, para garantir que os serviços estejam disponíveis corretamente durante a execução da aplicação

Clientes nomeados

Os clientes nomeados são instâncias de HttpClient que são configuradas com base em um nome específico. Esse nome permite que você crie diferentes configurações para cada cliente de maneira personalizada e referenciável. Por exemplo, ao configurar um cliente nomeado para a API jsonplaceholder, o código poderia ser o seguinte:

				
					builder.Services.AddHttpClient("JsonPlaceholder", client =>
{
    client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

				
			

Neste exemplo, o cliente nomeado “JsonPlaceholder” é configurado com a URL base e um cabeçalho padrão.

				
					public async Task<string> BuscarPostsAsync()
    {
        var client = _httpClientFactory.CreateClient("JsonPlaceholder");
        var response = await client.GetAsync("/posts");
        return await response.Content.ReadAsStringAsync();
    }

				
			

O método “CreateClient(“JsonPlaceholder”)” busca o cliente com o nome específico “JsonPlaceholder” e retorna uma instância do HttpClient com todas as configurações que você definiu. Isso permite que você faça requisições a essa API de maneira simplificada e consistente.  Essa abordagem não só reduz a duplicação de código, mas também facilita a manutenção e a escalabilidade da aplicação ao lidar com múltiplas APIs.

Clientes tipados

Os clientes tipados oferecem uma alternativa mais direta, onde o HttpClient é configurado e utilizado em uma classe específica. Diferente dos clientes nomeados, essa abordagem elimina a necessidade de fornecer um nome para o cliente. Em vez disso, o cliente é automaticamente associado à classe de serviço que o utiliza.

				
					builder.Services.AddHttpClient<IBlogService , BlogService>(client =>
{
    client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

				
			

A classe “BlogService” pode ser implementada da seguinte forma:

Cuidados ao usar clientes tipados em serviços singleton

Um ponto importante a ser observado é que clientes tipados são registrados com um tempo de vida transitório por padrão. Isso pode causar problemas se você tentar injetar um cliente tipado em um serviço singleton. O HttpClient ficará armazenado pelo tempo de vida do serviço singleton, o que pode levar a problemas de resolução de DNS. Para evitar isso, é recomendável configurar o tempo de vida das conexões utilizando o “SocketsHttpHandler”:

				
					services.AddHttpClient<IBlogService ,BlogService>(client =>
{
    client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    return new SocketsHttpHandler
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(5)
    };
});

				
			

Com essa abordagem, você garante que as conexões serão recicladas após um determinado tempo. Isso garante que, mesmo em um serviço singleton, as conexões do HttpClient sejam recicladas periodicamente, evitando problemas de conectividade ou DNS expirado.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui

Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

O IHttpClientFactory oferece uma maneira avançada e eficiente de trabalhar com HttpClient, evitando problemas comuns como esgotamento de portas e má gestão de DNS. Com ele, você pode criar clientes configurados para diferentes APIs e aproveitar recursos como clientes nomeados e clientes tipados para simplificar o código e a manutenção. Agora que você conhece as vantagens do IHttpClientFactory, pode implementar soluções mais robustas e escaláveis no seu projeto .NET!