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?


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.


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")
}

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()


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)

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.


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


-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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.