Menu

Cliente HTTP em Golang: GET, POST com JSON, timeouts e headers

Como fazer requisições HTTP em Go com net/http: http.Get, ler e fechar o corpo, verificar códigos de status, timeouts do http.Client, requisições com context e headers, parâmetros de query e POST com JSON.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Uma requisição GET

Todos os exemplos desta página iniciam o seu próprio servidor com httptest.NewServer e o chamam por uma conexão TCP real em localhost. Troque srv.URL por um endereço real como https://api.example.com e o código do cliente continua igual.

As três regras visíveis aqui:

  1. Verifique o err primeiro. Ele reporta falhas em obter qualquer resposta: erros de DNS, conexões recusadas, timeouts, problemas de TLS.
  2. defer resp.Body.Close() logo depois da verificação do erro. O corpo mantém a conexão aberta. Um corpo não fechado vaza a conexão.
  3. Leia ou decodifique o corpo. io.ReadAll para texto, json.NewDecoder(resp.Body).Decode(&v) para JSON.

Códigos de status não são erros

http.Get devolve err == nil para qualquer resposta que o servidor envie, incluindo 404 e 500. Você precisa verificar o status por conta própria:

io.LimitReader limita quanto de um corpo de erro você lê, para que um servidor que se comporta mal não faça você carregar megabytes em uma mensagem de erro. Muitas APIs devolvem 201, 202 ou 204 em caso de sucesso; verifique resp.StatusCode >= 200 && resp.StatusCode < 300 quando qualquer 2xx servir.

Timeouts: nunca use o cliente padrão para tráfego real

http.Get, http.Post e http.DefaultClient não têm timeout geral (o transporte padrão só limita a conexão e o handshake TLS). Um servidor que aceita a conexão e nunca responde bloqueia a sua goroutine para sempre. Crie o seu próprio cliente:

Client.Timeout cobre a troca inteira: conexão, redirecionamentos e leitura do corpo. O erro que ele devolve é um *url.Error que informa Timeout() == true, e o errors.As o encontra por meio da interface net.Error. Crie um cliente e reutilize-o em todo lugar; um http.Client é seguro para uso concorrente. O pool de conexões fica no Transport dele, não no Client: um http.Client{} novo por requisição ainda compartilha o http.DefaultTransport e o seu pool, mas um http.Transport novo por requisição começa com um pool vazio toda vez.

Requisições com context e headers

http.NewRequestWithContext monta uma requisição que você pode personalizar antes de enviar com client.Do. O context dá um prazo ou um cancelamento por requisição, além do timeout do cliente, e liga as chamadas de saída ao tempo de vida de uma requisição de entrada em um servidor.

Concatenar entrada do usuário em uma URL ("?q=" + q) quebra com &, # e espaços. O url.Values e o seu método Encode fazem o escape e ordenam as chaves. Para segmentos de caminho, use url.PathEscape.

POST com JSON

client.Post(url, contentType, body) é um atalho para uma requisição com aquele header Content-Type. Para qualquer outra coisa (um header de autenticação, PUT, PATCH, DELETE), use http.NewRequestWithContext com o método e client.Do. Para envio de formulários, client.PostForm(url, url.Values{...}) codifica os valores e define application/x-www-form-urlencoded.

Reutilização de conexões

O cliente só reutiliza uma conexão TCP quando você lê o corpo anterior até o fim e o fecha. Se você parar de ler antes (por exemplo, depois de um status de erro), esvazie o corpo antes de fechar quando fizer muitas requisições ao mesmo host:

defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()

Para corpos que podem ser enormes, esvazie por meio de um io.LimitReader, ou aceite perder a conexão.

Redirecionamentos

O cliente segue até 10 redirecionamentos automaticamente, e resp.Request.URL diz onde você foi parar. Para parar no primeiro redirecionamento, por exemplo para ler um header Location, defina CheckRedirect:

client := &http.Client{
	Timeout: 10 * time.Second,
	CheckRedirect: func(req *http.Request, via []*http.Request) error {
		return http.ErrUseLastResponse // return the 3xx response itself
	},
}

Erros comuns

  • Usar http.Get ou http.DefaultClient em produção. Sem timeout. Crie um http.Client com Timeout.
  • Esquecer o resp.Body.Close(). As conexões vazam até as requisições começarem a falhar.
  • Fechar o corpo antes de verificar o err. Quando o err não é nil, resp é nil e resp.Body.Close() causa panic.
  • Tratar err == nil como sucesso. Verifique resp.StatusCode.
  • Criar um http.Transport novo por requisição. Cada transporte tem o seu próprio pool de conexões, então toda requisição abre uma conexão nova (e um handshake TLS novo). Compartilhe um transporte, normalmente por meio de um único http.Client compartilhado.
  • Montar query strings à mão. Use url.Values.

Perguntas frequentes

Como fazer uma requisição GET em Go?

resp, err := http.Get(url), verifique o err, depois faça defer resp.Body.Close() e leia o corpo com io.ReadAll(resp.Body) ou decodifique-o com json.NewDecoder(resp.Body).Decode(&v). Verifique também resp.StatusCode: um 404 ou 500 não é um erro do http.Get.

Como definir um timeout em uma requisição HTTP em Go?

Crie um cliente com timeout, client := &http.Client{Timeout: 10 * time.Second}, e use-o no lugar de http.Get ou http.DefaultClient, que não têm timeout geral: um servidor que aceita a conexão e nunca responde bloqueia a chamada para sempre. Para um limite por requisição, monte a requisição com http.NewRequestWithContext e um context vindo de context.WithTimeout.

Como enviar uma requisição POST com corpo JSON em Go?

Faça o marshal do valor e depois o envie: body, _ := json.Marshal(v) e http.Post(url, "application/json", bytes.NewReader(body)). Para acrescentar headers como Authorization, monte-a com http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)), defina req.Header.Set("Content-Type", "application/json") e envie com client.Do(req).

Por que preciso fechar o resp.Body em Go?

O corpo é um fluxo sobre uma conexão aberta. Fechá-lo devolve a conexão ao pool para ser reutilizada; esquecer vaza conexões e descritores de arquivo até o programa chegar ao limite. Feche-o mesmo quando não for lê-lo, e leia até o fim se quiser que a conexão seja reutilizada.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR