C# - Você sabe mesmo utilizar o HttpClient corretamente?
Quando precisamos fazer uma requisição HTTP em C#, tudo parece muito simples. Basta usar o HttpClient, certo? E a primeira implementação que a maioria faz é algo assim:
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("accept", "application/json");
var response = await client.GetAsync($"{host}:{port}/{path}");
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseContent);
Com isso, achamos que está tudo resolvido e marcamos a tarefa como concluída. Funcionou, e se funcionou está tudo certo… será mesmo?
Bom, é aí que você se engana, meu caro adepto do CTRL+C e CTRL+V. Neste artigo, vou explicar os problemas dessa abordagem, focando no lado do cliente e como evitá-los para não se deparar com bugs difíceis de identificar.
Entendendo o problema
Antes de falar da solução, é importante entender como uma requisição HTTP funciona. De forma bem simplificada:
Toda requisição HTTP é feita usando sockets. Para abrir um socket, você precisa de um IP e uma PORTA. O cliente se conecta ao servidor, que está ouvindo na porta configurada, e após um “handshake” bem-sucedido (basicamente um “oi, vamos trocar pacotes?”), um socket é criado no cliente e outro no servidor. Esse socket fica ativo até o tempo configurado no Keep-Alive ou até ser encerrado.
Agora, qual o problema de criar um HttpClient novo a cada requisição?
A cada nova instância de HttpClient, o sistema é forçado a abrir um novo socket. Mesmo quando você acessa o mesmo servidor (IP e porta), isso resulta em um consumo excessivo de recursos tanto no cliente quanto no servidor.
Em cenários com alto volume de requisições, isso pode esgotar o limite de sockets do sistema operacional. Resultado? System.Net.Sockets.SocketException. E se você já enfrentou isso em produção, sabe que é um pesadelo identificar o que está causando essa exceção.
Como identificar o problema
Para visualizar o comportamento errado, você pode usar o comando tcpdump e observar as conexões sendo abertas:
15:21:49.112706 lo In IP localhost.5000 > localhost.54450: Flags [S.], seq 2951581501, ack 4180718991, win 65483, options [mss 65495,sackOK,TS val 2948923132 ecr 2948923132,nop,wscale 7], length 0
15:21:49.488534 lo In IP localhost.5000 > localhost.54458: Flags [S.], seq 3305744583, ack 3367141433, win 65483, options [mss 65495,sackOK,TS val 2948923508 ecr 2948923508,nop,wscale 7], length 0
Observe no resultado acima que foram criados dois sockets, um para cada requisição, mesmo que seja para o mesmo servidor. Esse comportamento é o principal sinal de que algo está errado.
A solução
A solução é reutilizar os sockets existentes sempre que possível. Isso pode ser feito de duas formas:
Usando uma instância estática de HttpClient para toda a aplicação. Utilizando o IHttpClientFactory para gerenciar as instâncias de HttpClient via injeção de dependência.
1. Instância estática de HttpClient
Essa abordagem é simples e funciona bem em cenários onde você precisa se conectar a poucos servidores. Exemplo:
public class HttpClientStaticService
{
private static readonly HttpClient Client = new HttpClient();
public async Task Request(string host, int port, string path)
{
...
var response = await Client.GetAsync($"{host}:{port}/{path}");
...
}
}
2. IHttpClientFactory
Se sua aplicação precisa lidar com múltiplos servidores ou configurações específicas para cada requisição, o IHttpClientFactory é a melhor escolha. Ele permite criar instâncias personalizadas, e ainda reutiliza os sockets. Exemplo:
public class HttpClientFactoryService
{
private readonly HttpClient _httpClient;
public HttpClientFactoryService(HttpClient httpClient)
=> _httpClient = httpClient;
public async Task GetWeatherForecastAsync()
{
...
var response = await _httpClient.GetAsync("weatherforecast");
...
}
}
Qual abordagem usar?
Depende do seu caso:
Instância estática: Ideal para aplicações simples onde poucas configurações são necessárias. IHttpClientFactory: Melhor para cenários mais complexos, onde múltiplas configurações são necessárias.
Conclusão
Saber como funciona o HttpClient e a reutilização de sockets não é só uma questão de performance, mas também para evitar erros difíceis de diagnosticar em produção. Escolher a melhor abordagem para o seu projeto evitará dores de cabeça no futuro.
Ah, e só para esclarecer: o que realmente se esgota não é o número de sockets, mas sim o limite de file descriptors (no caso de sistemas Unix/Linux). Mas isso é papo para outro artigo.
Confira o código no GitHub.
Se você gostou do conteúdo, curta e compartilhe sua opinião nos comentários! Fique à vontade para adicionar qualquer ponto ou sugestão que achar relevante.
Até mais, e fique com Deus!
🔗 Confira o código no GitHub.
🔗 Link para a postagem no Linkedin