Como definir o tempo limite para solicitações http.Get () no Golang?

106

Estou fazendo um buscador de URL no Go e tenho uma lista de URLs para buscar. Eu envio http.Get()solicitações para cada URL e obtenho sua resposta.

resp,fetch_err := http.Get(url)

Como posso definir um tempo limite personalizado para cada solicitação Get? (O tempo padrão é muito longo e isso torna meu buscador muito lento.) Eu quero que meu buscador tenha um tempo limite de cerca de 40-45 segundos, após o qual ele deve retornar "tempo limite da solicitação esgotado" e seguir para a próxima URL.

Como posso conseguir isso?

pymd
fonte
1
Só para avisar que achei essa forma mais conveniente (o tempo limite de discagem não funciona bem se houver problemas de rede, pelo menos para mim): blog.golang.org/context
Audrius
@Audrius Alguma ideia de por que o tempo limite de discagem não funciona quando há problemas de rede? Acho que estou vendo a mesma coisa. Achei que era para isso que DialTimeout servia?!?!
Jordan
@Jordan É difícil dizer, já que não mergulhei tão fundo no código da biblioteca. Eu postei minha solução abaixo. Estou usando em produção há um bom tempo e até agora ele "simplesmente funciona" (tm).
Audrius

Respostas:

255

Aparentemente no Go 1.3 http.Client tem o campo Timeout

client := http.Client{
    Timeout: 5 * time.Second,
}
client.Get(url)

Isso fez o truque para mim.

sparrovv
fonte
10
Bem, isso é bom o suficiente para mim. Que bom que rolei um pouco para baixo :)
James Adam
5
Existe uma maneira de ter um tempo limite diferente por solicitação?
Arnaud Rinquin
11
O que acontece quando o tempo limite é atingido? Retorna Getum erro? Estou um pouco confuso porque o Godoc para Clientdiz: O cronômetro continua em execução após o retorno Get, Head, Post ou Do e interromperá a leitura do Response.Body. Então, isso significa que a leitura Get ouResponse.Body pode ser interrompida por um erro?
Avi Flax
1
Pergunta, qual é a diferença entre http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Roy Lee de
2
@Roylee Uma das principais diferenças de acordo com a documentação: http.Client.Timeoutinclui o tempo de leitura do corpo da resposta, http.Transport.ResponseHeaderTimeoutnão inclui.
imwill
53

Você precisa configurar seu próprio Cliente com seu próprio Transporte, que usa uma função de discagem personalizada que envolve DialTimeout .

Algo como (completamente não testado ) isto :

var timeout = time.Duration(2 * time.Second)

func dialTimeout(network, addr string) (net.Conn, error) {
    return net.DialTimeout(network, addr, timeout)
}

func main() {
    transport := http.Transport{
        Dial: dialTimeout,
    }

    client := http.Client{
        Transport: &transport,
    }

    resp, err := client.Get("http://some.url")
}
Volker
fonte
Muito obrigado! Isso é exatamente o que eu estava procurando.
dia
qual é a vantagem de usar o net.DialTimeout sobre o Transport.ResponseHeaderTimeout descrito pela resposta do zzzz?
Daniele B
4
@Daniel B: Você está fazendo a pergunta errada. Não se trata de vantagens, mas sim do que cada código faz. DialTimeouts entram em ação se o servidor não puder ser dailed, enquanto outros timeouts são ativados se certas operações na conexão estabelecida demorarem muito. Se seus servidores de destino estabelecerem uma conexão rapidamente, mas depois começarem a banir você lentamente, um tempo limite de discagem não ajudará.
Volker
1
@Volker, obrigado pela sua resposta. Na verdade eu percebi isso também: parece que o Transport.ResponseHeaderTimeout define o tempo limite de leitura, que é o tempo limite depois que a conexão foi estabelecida, enquanto o seu é um tempo limite de discagem. A solução de dmichael lida com o tempo limite de discagem e o tempo limite de leitura.
Daniele B de
1
@Jonno: Não há elencos no Go. Estas são conversões de tipo.
Volker
31

Para adicionar à resposta de Volker, se você também gostaria de definir o tempo limite de leitura / gravação além do tempo limite de conexão, você pode fazer algo como o seguinte

package httpclient

import (
    "net"
    "net/http"
    "time"
)

func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
    return func(netw, addr string) (net.Conn, error) {
        conn, err := net.DialTimeout(netw, addr, cTimeout)
        if err != nil {
            return nil, err
        }
        conn.SetDeadline(time.Now().Add(rwTimeout))
        return conn, nil
    }
}

func NewTimeoutClient(connectTimeout time.Duration, readWriteTimeout time.Duration) *http.Client {

    return &http.Client{
        Transport: &http.Transport{
            Dial: TimeoutDialer(connectTimeout, readWriteTimeout),
        },
    }
}

Este código foi testado e está funcionando em produção. A essência completa com testes está disponível aqui https://gist.github.com/dmichael/5710968

Esteja ciente de que você precisará criar um novo cliente para cada solicitação por causa do conn.SetDeadlinequal faz referência a um ponto no futuro detime.Now()

dmichael
fonte
Você não deveria verificar o valor de retorno de conn.SetDeadline?
Eric Urban
3
Este tempo limite não funciona com conexões keep-alive, que é o padrão e o que a maioria das pessoas deveria estar usando, imagino. Aqui está o que eu descobri para lidar com isso: gist.github.com/seantalts/11266762
xitrium
Obrigado @xitrium e Eric pela contribuição adicional.
dmichael
Acho que não é como você disse que precisaremos criar um novo cliente para cada solicitação. Já que Dial é uma função que eu acho que recebe chamada toda vez que você envia cada requisição novamente no mesmo cliente.
A-letubby
Tem certeza de que precisa de um novo cliente a cada vez? Cada vez que estiver discando, em vez de usar net.Dial, ele usará a função que o TimeoutDialer constrói. Essa é uma nova conexão, com o deadline avaliado a cada vez, a partir de um novo horário. Chamada agora ().
Blake Caldwell
16

Se você quiser fazer isso por solicitação, tratamento de erros ignorado por questões de brevidade:

ctx, cncl := context.WithTimeout(context.Background(), time.Second*3)
defer cncl()

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://google.com", nil)

resp, _ := http.DefaultClient.Do(req)
Chad Grant
fonte
1
Informação extra: por doc, o prazo imposto pelo Contexto abrange também a leitura do Corpo, à semelhança do http.Client.Timeout.
kubanczyk
1
Deve ser uma resposta aceita para Go 1.7+. Para Go 1.13+ pode ser ligeiramente reduzido usando NewRequestWithContext
kubanczyk
9

Uma maneira rápida e suja:

http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45

Este é um estado global mutante sem qualquer coordenação. No entanto, pode ser adequado para seu coletor de urls. Caso contrário, crie uma instância privada de http.RoundTripper:

var myTransport http.RoundTripper = &http.Transport{
        Proxy:                 http.ProxyFromEnvironment,
        ResponseHeaderTimeout: time.Second * 45,
}

var myClient = &http.Client{Transport: myTransport}

resp, err := myClient.Get(url)
...

Nada acima foi testado.

zzzz
fonte
Por favor, alguém me corrija, mas parece que o ResponseHeaderTimeout é sobre o tempo limite de leitura, ou seja, o tempo limite depois que a conexão foi estabelecida. A solução mais abrangente parece ser a de @dmichael, pois permite definir o tempo limite de discagem e o tempo limite de leitura.
Daniele B
http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45me ajude muito no teste de gravação para o tempo limite do pedido. Muito obrigado.
Lee
0

Você pode usar https://github.com/franela/goreq, que lida com os tempos limite de uma maneira simples.

marcosnils
fonte
Esta biblioteca parece incrível!
ChrisR
-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Isso pode ajudar, mas observe que ResponseHeaderTimeoutcomeça somente depois que a conexão é estabelecida.

T.Max
fonte