O que é a reutilização de conexões HTTP/1 no Go 1.27

Uma chamada HTTP não precisa abrir uma conexão TCP nova a cada requisição. Quando cliente e servidor mantêm a conexão aberta, várias requisições podem usar o mesmo canal. Esse comportamento reduz trabalho de rede e costuma ser importante em APIs que fazem muitas chamadas pequenas.

O tema ganhou destaque com a publicação do blog oficial do Go sobre a reutilização de conexões HTTP/1 no Go 1.27. O foco não é trocar o protocolo da aplicação, mas melhorar a forma como o transporte padrão aproveita conexões existentes.

Para quem desenvolve em Go, isso interessa porque o pacote net/http já é a base de grande parte dos clientes e servidores. Uma configuração correta pode reduzir latência e conexões abertas sem exigir uma biblioteca externa.

Como funciona

O cliente HTTP mantém um conjunto de conexões por destino. Ao fazer uma nova requisição, o transporte tenta encontrar uma conexão ociosa compatível. Se encontrar, envia a requisição por ela; caso contrário, abre uma conexão nova.

Em HTTP/1.1, as requisições usam uma conexão por vez em cada canal. Depois que a resposta é lida e o corpo é fechado corretamente, a conexão pode voltar para o conjunto de ociosas. Se o corpo não for consumido, essa reutilização pode não acontecer.

O http.Transport controla detalhes como limite total de conexões ociosas, limite por host e tempo de permanência. O Go 1.27 trata a reutilização de HTTP/1 com melhorias destacadas na documentação oficial, mas o resultado ainda depende do servidor, da rede e do padrão de tráfego.

💡
Dica

Sempre feche o corpo da resposta. Quando o conteúdo for necessário, leia o corpo antes de fechá-lo para dar ao transporte a melhor chance de reutilizar a conexão.

Principais recursos

O primeiro recurso é o pool de conexões ociosas por destino. Ele evita o custo de repetir o handshake TCP em chamadas consecutivas para o mesmo serviço, especialmente em integrações entre APIs.

O segundo é o controle explícito por meio de http.Transport. Você pode definir limites coerentes com o volume de tráfego, em vez de aceitar uma configuração genérica para todos os ambientes.

O terceiro é a observabilidade do comportamento. Métricas de latência, erros, conexões ativas e tempo de resposta ajudam a verificar se a aplicação está reutilizando canais ou abrindo conexões em excesso.

  • Menos handshakes repetidos em chamadas ao mesmo host.
  • Maior controle sobre concorrência e conexões ociosas.
  • Compatibilidade com o cliente HTTP padrão do Go.

Como começar: instalação ou acesso passo a passo

Comece lendo a publicação oficial do Go sobre o assunto e confirme a versão usada pelo projeto. O comando abaixo mostra a versão instalada no ambiente local.

go version

Depois, identifique os clientes HTTP usados pelo serviço. Prefira reutilizar uma instância de http.Client em vez de criar um cliente novo para cada requisição. O cliente pode compartilhar o transporte e o pool de conexões.

var client = &http.Client{
    Timeout: 10 * time.Second,
}

Por fim, teste o comportamento em um ambiente controlado. Compare latência, quantidade de conexões e taxa de erro antes e depois de ajustar o transporte. Não copie limites de outro sistema sem considerar o tamanho das respostas e o número de destinos.

Exemplo prático

Imagine um serviço Go que consulta uma API de catálogo várias vezes durante o processamento de um pedido. Criar um cliente compartilhado permite que o transporte reaproveite conexões com o mesmo host.

transport := &http.Transport{
    MaxIdleConns:        100,
    MaxIdleConnsPerHost: 20,
    IdleConnTimeout:     90 * time.Second,
}

client := &http.Client{
    Transport: transport,
    Timeout:   10 * time.Second,
}

Na chamada, trate erros e feche o corpo. Se a aplicação precisa do conteúdo, leia a resposta antes de liberar o recurso. Esse detalhe é pequeno, mas evita desperdiçar o trabalho feito pelo transporte.

resp, err := client.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
    return err
}
_ = body

Para validar o resultado, faça um teste de carga com o mesmo padrão de requisições e observe a mediana e os percentis de latência. A comparação só é útil se o servidor remoto, o volume e o tamanho das respostas forem equivalentes.

Comparação com alternativas

HTTP/1 continua sendo uma escolha prática quando a API já depende dele, quando a infraestrutura é simples ou quando a compatibilidade com servidores antigos importa. A reutilização reduz o custo de chamadas repetidas sem mudar o contrato da API.

HTTP/2 permite multiplexar várias requisições na mesma conexão e pode ser melhor para muitos fluxos simultâneos. Porém, exige suporte adequado no cliente, no servidor e nos proxies do caminho.

HTTP/3 usa QUIC sobre UDP e pode ajudar em redes com perdas ou mobilidade, mas adiciona requisitos de infraestrutura. A escolha deve partir de medições, não apenas do número da versão.

  • HTTP/1: compatibilidade ampla e configuração direta.
  • HTTP/2: multiplexação para tráfego concorrente.
  • HTTP/3: transporte moderno baseado em QUIC.

Pontos positivos e limitações

O principal ponto positivo é aproveitar uma capacidade já presente no cliente padrão do Go. A equipe pode ganhar eficiência sem introduzir dependências externas ou reescrever todas as integrações.

Outro benefício é o controle fino. Limites por host, tempo de ociosidade e timeout ajudam a equilibrar desempenho, memória e pressão sobre o serviço remoto.

A limitação é que nenhuma configuração corrige um servidor que fecha conexões imediatamente ou uma aplicação que abandona corpos de resposta. Além disso, conexões ociosas demais podem consumir recursos e manter sockets abertos além do necessário.

⚠️
Atenção

Mais conexões não significa automaticamente mais desempenho. O limite correto depende do servidor remoto, da concorrência real e dos limites de arquivo do ambiente.

Casos de uso reais

Um agregador de preços pode consultar vários endpoints do mesmo fornecedor durante cada busca. A reutilização ajuda a reduzir o custo repetido de abrir conexões para esse fornecedor.

Um worker de filas que envia eventos para uma API de observabilidade também pode manter um cliente compartilhado. Nesse caso, o benefício aparece quando os lotes são frequentes e o destino é estável.

Um backend que chama serviços internos do mesmo cluster pode usar limites por host diferentes dos limites usados para serviços externos. A topologia e a latência de cada destino devem orientar a configuração.

Em scripts de uso ocasional, o ganho pode ser pequeno. Para esses casos, simplicidade e timeout correto costumam ser mais importantes que ajustes avançados.

Dicas e boas práticas

💡
Dica

Crie o cliente uma vez e injete-o nas funções que fazem chamadas. Evite instanciar um novo transporte dentro de cada requisição.

🚀
Pro tip

Meça por destino. Um único MaxIdleConnsPerHost pode ser adequado para um fornecedor e exagerado para outro.

🔴
Cuidado

Nunca deixe respostas sem tratamento. Verifique o erro, feche o corpo e aplique timeout para não acumular recursos em chamadas travadas.

Também vale registrar métricas separadas para conexão nova, reutilização, timeout e resposta do servidor. Sem essa separação, uma melhora ou regressão pode ficar escondida na média geral.

Vale a pena?

Vale a pena investigar a melhoria do Go 1.27 se seu serviço faz muitas chamadas HTTP/1 para os mesmos destinos. O transporte padrão oferece recursos suficientes para começar com segurança e medir o efeito.

Não é necessário alterar tudo de uma vez. Atualize o ambiente de teste, mantenha o cliente compartilhado, valide o fechamento dos corpos e compare métricas com uma carga representativa.

O próximo passo é ler a publicação oficial do Go, criar um teste reproduzível e só então ajustar os limites do transporte. Assim, a decisão fica baseada no comportamento da sua aplicação, não em uma promessa genérica de performance.