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?


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.


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

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


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)

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.


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


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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.