Comment définir le délai d'expiration des requêtes http.Get () dans Golang?


106

Je crée un récupérateur d'URL dans Go et j'ai une liste d'URL à récupérer. J'envoie des http.Get()requêtes à chaque URL et j'obtiens leur réponse.

resp,fetch_err := http.Get(url)

Comment puis-je définir un délai d'expiration personnalisé pour chaque demande Get? (Le temps par défaut est très long et cela rend mon récupérateur vraiment lent.) Je veux que mon récupérateur ait un délai d'attente d'environ 40 à 45 secondes après quoi il devrait renvoyer "requête expirée" et passer à l'URL suivante.

Comment puis-je atteindre cet objectif?


1
Juste pour vous faire savoir que j'ai trouvé cela plus pratique (le délai de numérotation ne fonctionne pas bien s'il y a des problèmes de réseau, du moins pour moi): blog.golang.org/context
Audrius

@Audrius Une idée de la raison pour laquelle le délai de numérotation ne fonctionne pas en cas de problèmes de réseau? Je pense que je vois la même chose. Je pensais que c'était pour ça que DialTimeout était?!?!
Jordan

@ Jordan Difficile à dire car je n'ai pas plongé aussi profondément dans le code de la bibliothèque. J'ai posté ma solution ci-dessous. Je l'utilise en production depuis un certain temps maintenant et jusqu'à présent, cela "fonctionne juste" (tm).
Audrius

Réponses:


255

Apparemment, dans Go 1.3, http.Client a un champ Timeout

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

Cela a fait l'affaire pour moi.


10
Eh bien, c'est assez bien pour moi. Heureux d'avoir fait défiler un peu vers le bas :)
James Adam

5
Existe-t-il un moyen d'avoir un délai d'expiration différent par demande?
Arnaud Rinquin

11
Que se passe-t-il lorsque le délai d'expiration arrive? Renvoie-t-il Getune erreur? Je suis un peu confus car le Godoc pour Clientdit: Le minuteur continue de fonctionner après le retour de Get, Head, Post ou Do et interrompra la lecture de Response.Body. Cela signifie-t-il que l' un Get ou l' autre ou la lecture Response.Bodypourrait être interrompu par une erreur?
Avi Flax

1
Question, quelle est la différence entre http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Roy Lee

2
@Roylee L'une des principales différences selon la documentation: http.Client.Timeoutinclut le temps de lecture du corps de la réponse, http.Transport.ResponseHeaderTimeoutne l'inclut pas.
imwill

53

Vous devez configurer votre propre client avec votre propre transport qui utilise une fonction de numérotation personnalisée qui entoure DialTimeout .

Quelque chose comme (complètement non testé ) ceci :

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

Merci beaucoup! C'est exactement ce que je recherchais.
pymd

quel est l'avantage d'utiliser le net.DialTimeout sur le Transport.ResponseHeaderTimeout décrit par la réponse de zzzz?
Daniele B

4
@Daniel B: Vous posez la mauvaise question. Il ne s'agit pas d'avantages mais de ce que fait chaque code. DialTimeouts intervient si le serveur ne peut pas être exécuté tandis que d'autres délais d'attente se déclenchent si certaines opérations sur la connexion établie prennent trop de temps. Si vos serveurs cibles établissent une connexion rapidement mais commencent ensuite à vous interdire lentement, un délai d'attente de numérotation n'aidera pas.
Volker

1
@Volker, merci pour votre réponse. En fait, je m'en suis rendu compte aussi: il ressemble à Transport.ResponseHeaderTimeout définit le délai de lecture, c'est-à-dire le délai après l'établissement de la connexion, alors que vous êtes un délai de numérotation. La solution de dmichael traite à la fois du délai de numérotation et du délai de lecture.
Daniele B

1
@Jonno: Il n'y a pas de lancers dans Go. Ce sont des conversions de type.
Volker

31

Pour ajouter à la réponse de Volker, si vous souhaitez également définir le délai de lecture / écriture en plus du délai de connexion, vous pouvez faire quelque chose comme ce qui suit

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

Ce code est testé et fonctionne en production. L'essentiel avec les tests est disponible ici https://gist.github.com/dmichael/5710968

Sachez que vous devrez créer un nouveau client pour chaque demande en raison du conn.SetDeadlinequi fait référence à un point dans le futur à partir detime.Now()


Ne devriez-vous pas vérifier la valeur de retour de conn.SetDeadline?
Eric Urban

3
Ce délai d'expiration ne fonctionne pas avec les connexions keepalive, qui est la valeur par défaut et ce que la plupart des gens devraient utiliser, j'imagine. Voici ce que j'ai proposé pour résoudre ce problème
xitrium

Merci @xitrium et Eric pour la contribution supplémentaire.
dmichael

J'ai l'impression que ce n'est pas comme vous l'avez dit que nous devrons créer un nouveau client pour chaque demande. Depuis Dial est une fonction que je pense qu'elle est appelée à chaque fois que vous envoyez chaque demande dans le même client.
A-letubby le

Vous êtes sûr d'avoir besoin d'un nouveau client à chaque fois? Chaque fois qu'il compose, au lieu d'utiliser net.Dial, il utilisera la fonction que TimeoutDialer construit. C'est une nouvelle connexion, avec la date limite évaluée à chaque fois, à partir d'un nouvel appel time.Now ().
Blake Caldwell

16

Si vous voulez le faire par requête, la gestion des erreurs est ignorée par souci de concision:

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
Info supplémentaire: par doc, la date limite imposée par Context englobe également la lecture du Body, de la même manière que le http.Client.Timeout.
kubanczyk

1
Devrait être une réponse acceptée pour Go 1.7+. For Go 1.13+ peut être légèrement raccourci en utilisant NewRequestWithContext
kubanczyk

9

Une manière rapide et sale:

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

C'est un état mondial en mutation sans aucune coordination. Pourtant, cela peut convenir à votre récupérateur d'URL. Sinon, créez une instance privée 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)
...

Rien ci-dessus n'a été testé.


S'il vous plaît, corrigez-moi, mais il semble que ResponseHeaderTimeout concerne le délai de lecture, c'est-à-dire le délai après l'établissement de la connexion. La solution la plus complète semble être celle de @dmichael, car elle permet de définir à la fois le délai de numérotation et le délai de lecture.
Daniele B du

http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45Aidez-moi beaucoup à écrire un test pour le délai d'expiration de la demande. Merci beaucoup.
lee


-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Cela peut aider, mais remarquez que cela ResponseHeaderTimeoutne démarre qu'après l'établissement de la connexion.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.