¿Cómo configurar el tiempo de espera para las solicitudes http.Get () en Golang?

106

Estoy creando un buscador de URL en Go y tengo una lista de URL para buscar. Envío http.Get()solicitudes a cada URL y obtengo su respuesta.

resp,fetch_err := http.Get(url)

¿Cómo puedo establecer un tiempo de espera personalizado para cada solicitud de obtención? (El tiempo predeterminado es muy largo y eso hace que mi buscador sea muy lento.) Quiero que mi buscador tenga un tiempo de espera de alrededor de 40-45 segundos, después de lo cual debería devolver "solicitud agotada" y pasar a la siguiente URL.

¿Cómo puedo conseguir esto?

pymd
fuente
1
Solo les hago saber que esto me pareció más conveniente (el tiempo de espera de marcación no funciona bien si hay problemas de red, al menos para mí): blog.golang.org/context
Audrius
@Audrius ¿Alguna idea de por qué el tiempo de espera de marcación no funciona cuando hay problemas de red? Creo que estoy viendo lo mismo. Pensé que para eso era DialTimeout?!?!
Jordania
@Jordan Es difícil de decir ya que no profundicé tanto en el código de la biblioteca. He publicado mi solución a continuación. Lo estoy usando en producción desde hace bastante tiempo y hasta ahora "simplemente funciona" (tm).
Audrius

Respuestas:

255

Aparentemente, en Go 1.3 http.El cliente tiene un campo de tiempo de espera

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

Eso me ha funcionado.

Sparrovv
fuente
10
Bueno, eso es suficiente para mí. Me alegro de haberme desplazado un poco hacia abajo :)
James Adam
5
¿Hay alguna forma de tener un tiempo de espera diferente por solicitud?
Arnaud Rinquin
11
¿Qué sucede cuando llega el tiempo de espera? ¿ GetDevuelve un error? Estoy un poco confundido porque Godoc para Clientdice: El temporizador sigue funcionando después de que Get, Head, Post o Do regresen e interrumpirá la lectura de Response.Body. Entonces, ¿eso significa que la lectura Get o la lectura Response.Bodypodrían verse interrumpidas por un error?
Avi Flax
1
Pregunta, ¿cuál es la diferencia entre http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Roy Lee
2
@Roylee Una de las principales diferencias según los documentos: http.Client.Timeoutincluye el tiempo para leer el cuerpo de la respuesta, http.Transport.ResponseHeaderTimeoutno lo incluye.
Imwill
53

Debe configurar su propio cliente con su propio transporte, que utiliza una función de marcado personalizada que envuelve DialTimeout .

Algo como (completamente no probado ) esto :

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
fuente
¡Muchas gracias! Esto es exactamente lo que estaba buscando.
pymd
¿Cuál es la ventaja de usar net.DialTimeout sobre Transport.ResponseHeaderTimeout descrito por la respuesta de zzzz?
Daniele B
4
@Daniel B: Estás haciendo la pregunta incorrecta. No se trata de ventajas sino de lo que hace cada código. DialTimeouts se activa si el servidor no se puede interrumpir, mientras que otros tiempos de espera se activan si ciertas operaciones en la conexión establecida toman demasiado tiempo. Si sus servidores de destino establecen una conexión rápidamente pero luego comienzan a prohibirlo lentamente, el tiempo de espera de marcación no ayudará.
Volker
1
@Volker, gracias por tu respuesta. En realidad, también me di cuenta: parece que Transport.ResponseHeaderTimeout establece el tiempo de espera de lectura, que es el tiempo de espera después de que se ha establecido la conexión, mientras que su es un tiempo de espera de marcación. La solución de dmichael se ocupa del tiempo de espera de marcación y de lectura.
Daniele B
1
@Jonno: No hay elencos en Go. Estas son conversiones de tipo.
Volker
31

Para agregar a la respuesta de Volker, si también desea establecer el tiempo de espera de lectura / escritura además del tiempo de espera de conexión, puede hacer algo como lo siguiente

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 está probado y está funcionando en producción. La esencia completa con las pruebas está disponible aquí https://gist.github.com/dmichael/5710968

Tenga en cuenta que deberá crear un nuevo cliente para cada solicitud debido a conn.SetDeadlineque hace referencia a un punto en el futuro desdetime.Now()

dmichael
fuente
¿No debería comprobar el valor de retorno de conn.SetDeadline?
Eric Urban
3
Este tiempo de espera no funciona con conexiones keepalive, que es el valor predeterminado y lo que la mayoría de la gente debería usar, imagino. Esto es lo que se me ocurrió para lidiar con esto: gist.github.com/seantalts/11266762
xitrium
Gracias @xitrium y Eric por la aportación adicional.
dmichael
Siento que no es como usted dijo que tendremos que crear un nuevo cliente para cada solicitud. Dado que Dial es una función que creo que recibe una llamada cada vez que envía cada solicitud en el mismo cliente.
A-letubby
¿Estás seguro de que necesitas un nuevo cliente cada vez? Cada vez que esté marcando, en lugar de usar net.Dial, usará la función que TimeoutDialer construye. Esa es una nueva conexión, con la fecha límite evaluada cada vez, desde un momento nuevo. Ahora () llame.
Blake Caldwell
16

Si desea hacerlo por solicitud, se ignora el manejo de errores por brevedad:

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)
Beca Chad
fuente
1
Información adicional: por documento, la fecha límite impuesta por Context también incluye la lectura del Cuerpo, al igual que el http.Client.Timeout.
kubanczyk
1
Debería ser una respuesta aceptada para Go 1.7+. Para Go 1.13+ se puede acortar ligeramente usando NewRequestWithContext
kubanczyk
9

Una forma rápida y sucia:

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

Este es un estado global mutante sin ninguna coordinación. Sin embargo, posiblemente esté bien para su buscador de URL. De lo contrario, cree una instancia 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 de lo anterior fue probado.

zzzz
fuente
Por favor, que alguien me corrija, pero parece que ResponseHeaderTimeout se trata del tiempo de espera de lectura, que es el tiempo de espera después de que se ha establecido la conexión. La solución más completa parece ser la de @dmichael, ya que permite establecer tanto el tiempo de espera de marcación como el tiempo de espera de lectura.
Daniele B
http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45Ayúdame mucho en la prueba de escritura para solicitar el tiempo de espera. Muchas gracias.
lee
-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Esto puede ayudar, pero tenga en cuenta que ResponseHeaderTimeoutcomienza solo después de que se establece la conexión.

T.Max
fuente