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:
- Verifique o
errprimeiro. Ele reporta falhas em obter qualquer resposta: erros de DNS, conexões recusadas, timeouts, problemas de TLS. 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.- Leia ou decodifique o corpo.
io.ReadAllpara 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.Getouhttp.DefaultClientem produção. Sem timeout. Crie umhttp.ClientcomTimeout. - 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 oerrnão énil,respénileresp.Body.Close()causa panic. - Tratar
err == nilcomo sucesso. Verifiqueresp.StatusCode. - Criar um
http.Transportnovo 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 únicohttp.Clientcompartilhado. - 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.