Wie kann ich ein Zeitlimit für http.Get () -Anfragen in Golang festlegen?

106

Ich mache einen URL-Abruf in Go und habe eine Liste der abzurufenden URLs. Ich sende http.Get()Anfragen an jede URL und erhalte deren Antwort.

resp,fetch_err := http.Get(url)

Wie kann ich für jede Get-Anfrage ein benutzerdefiniertes Zeitlimit festlegen? (Die Standardzeit ist sehr lang und das macht meinen Abruf sehr langsam.) Ich möchte, dass mein Abruf eine Zeitüberschreitung von ca. 40-45 Sekunden hat. Danach sollte er "Zeitüberschreitung bei Anforderung" zurückgeben und zur nächsten URL übergehen.

Wie kann ich das erreichen?

pymd
quelle
1
Ich möchte euch nur wissen lassen, dass ich diesen Weg bequemer fand (das Wählzeitlimit funktioniert nicht gut, wenn es Netzwerkprobleme gibt, zumindest für mich): blog.golang.org/context
Audrius
@Audrius Irgendeine Idee, warum das Wählzeitlimit bei Netzwerkproblemen nicht funktioniert? Ich glaube, ich sehe das Gleiche. Ich dachte, dafür ist DialTimeout gedacht?!?!
Jordanien
@ Jordan Schwer zu sagen, da ich nicht so tief in den Bibliothekscode eingetaucht bin. Ich habe meine Lösung unten veröffentlicht. Ich benutze es jetzt schon eine ganze Weile in der Produktion und bis jetzt "funktioniert es einfach" (tm).
Audrius

Antworten:

255

Anscheinend hat http.Client in Go 1.3 das Feld Timeout

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

Das ist der Trick für mich.

sparrovv
quelle
10
Das ist gut genug für mich. Ich bin froh, dass ich ein bisschen nach unten gescrollt habe :)
James Adam
5
Gibt es eine Möglichkeit, pro Anfrage ein anderes Timeout zu haben?
Arnaud Rinquin
10
Was passiert, wenn das Timeout eintritt? Gibt Geteinen Fehler zurück? Ich bin ein wenig verwirrt, weil der Godoc für Clientsagt: Der Timer läuft nach Get, Head, Post oder Do return weiter und unterbricht das Lesen der Response.Body. Bedeutet das also, dass entweder Get oder das Lesen Response.Bodydurch einen Fehler unterbrochen werden könnte?
Avi Flax
1
Frage, was ist der Unterschied zwischen http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Roy Lee
2
@ Roylee Einer der Hauptunterschiede laut Dokumentation: Beinhaltet http.Client.Timeoutdie Zeit zum Lesen des Antworttextes, http.Transport.ResponseHeaderTimeoutschließt ihn nicht ein.
Imwill
53

Sie müssen Ihren eigenen Client mit Ihrem eigenen Transport einrichten, der eine benutzerdefinierte Wählfunktion verwendet, die DialTimeout umschließt .

So etwas wie (völlig ungetestet ) diese :

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
quelle
Vielen Dank! Das ist genau das, wonach ich gesucht habe.
Pymd
Was ist der Vorteil der Verwendung des net.DialTimeout gegenüber dem Transport.ResponseHeaderTimeout, der in der Antwort des zzzz beschrieben wird?
Daniele B
4
@ Daniel B: Sie stellen die falsche Frage. Es geht nicht um Vorteile, sondern darum, was jeder Code tut. DialTimeouts springen ein, wenn der Server nicht beschädigt werden kann, während andere Timeouts aktiviert werden, wenn bestimmte Vorgänge auf der hergestellten Verbindung zu lange dauern. Wenn Ihre Zielserver schnell eine Verbindung herstellen, Sie dann jedoch langsam sperren, hilft eine Wählzeitüberschreitung nicht weiter.
Volker
1
@Volker, danke für deine Antwort. Eigentlich habe ich es auch erkannt: Es sieht so aus, als ob Transport.ResponseHeaderTimeout das Lesezeitlimit festlegt, dh das Zeitlimit nach dem Herstellen der Verbindung, während es sich bei Ihrem um ein Wählzeitlimit handelt. Die Lösung von dmichael behandelt sowohl das Wählzeitlimit als auch das Lesezeitlimit.
Daniele B
1
@Jonno: Es gibt keine Casts in Go. Dies sind Typkonvertierungen.
Volker
31

Um die Antwort von Volker zu ergänzen, können Sie Folgendes tun, wenn Sie zusätzlich zum Zeitlimit für die Verbindung auch das Lese- / Schreibzeitlimit festlegen möchten

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

Dieser Code wird getestet und funktioniert in der Produktion. Die vollständige Liste der Tests finden Sie hier https://gist.github.com/dmichael/5710968

Beachten Sie, dass Sie für jede Anforderung einen neuen Client erstellen müssen, da conn.SetDeadlinedieser auf einen Punkt in der Zukunft verweisttime.Now()

dmichael
quelle
Sollten Sie nicht den Rückgabewert von conn.SetDeadline überprüfen?
Eric Urban
3
Dieses Timeout funktioniert nicht mit Keepalive-Verbindungen. Dies ist die Standardeinstellung und das, was die meisten Leute verwenden sollten, stelle ich mir vor. Hier ist, was ich mir
ausgedacht habe,
Vielen Dank an @xitrium und Eric für die zusätzliche Eingabe.
Dmichael
Ich denke, es ist nicht so, wie Sie gesagt haben, dass wir für jede Anfrage einen neuen Kunden erstellen müssen. Da Dial eine Funktion ist, die meines Erachtens jedes Mal erneut aufgerufen wird, senden Sie jede Anfrage an denselben Client.
A-letubby
Sind Sie sicher, dass Sie jedes Mal einen neuen Kunden benötigen? Jedes Mal, wenn gewählt wird, wird anstelle von net.Dial die von TimeoutDialer erstellte Funktion verwendet. Dies ist eine neue Verbindung, bei der die Frist jedes Mal ab einem neuen Zeitpunkt ausgewertet wird. Now () -Aufruf.
Blake Caldwell
16

Wenn Sie dies auf Anfrage tun möchten, wird die Fehlerbehandlung der Kürze halber ignoriert:

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
quelle
1
Zusätzliche Informationen: Pro Dokument umfasst die vom Kontext festgelegte Frist auch das Lesen des Körpers, ähnlich wie beim http.Client.Timeout.
Kubanczyk
1
Sollte eine akzeptierte Antwort für Go 1.7+ sein. For Go 1.13+ kann mit NewRequestWithContext
kubanczyk
9

Ein schneller und schmutziger Weg:

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

Dies mutiert den globalen Staat ohne jegliche Koordination. Möglicherweise ist es jedoch für Ihren URL-Abruf in Ordnung. Andernfalls erstellen Sie eine private Instanz von 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)
...

Nichts oben wurde getestet.

zzzz
quelle
Bitte korrigiert mich jemand, aber es sieht so aus, als ob es sich beim ResponseHeaderTimeout um das Lesezeitlimit handelt, dh um das Zeitlimit nach dem Herstellen der Verbindung. Die umfassendste Lösung scheint die von @dmichael zu sein, da hier sowohl das Wählzeitlimit als auch das Lesezeitlimit festgelegt werden können.
Daniele B
http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45Helfen Sie mir viel beim Schreiben eines Tests für das Zeitlimit für Anfragen. Vielen Dank.
Lee
0

Sie können https://github.com/franela/goreq verwenden , um Zeitüberschreitungen auf einfache und einfache Weise zu behandeln.

marcosnils
quelle
Diese Bibliothek sieht toll aus!
ChrisR
-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Dies kann hilfreich sein, aber beachten Sie, dass dies ResponseHeaderTimeouterst beginnt, nachdem die Verbindung hergestellt wurde.

T. Max
quelle