Come impostare il timeout per le richieste http.Get () in Golang?


106

Sto creando un URL fetcher in Go e ho un elenco di URL da recuperare. Invio http.Get()richieste a ciascun URL e ottengo la loro risposta.

resp,fetch_err := http.Get(url)

Come posso impostare un timeout personalizzato per ogni richiesta Get? (Il tempo predefinito è molto lungo e questo rende il mio fetcher molto lento.) Voglio che il mio fetcher abbia un timeout di circa 40-45 secondi dopo di che dovrebbe restituire "richiesta scaduta" e passare all'URL successivo.

Come posso raggiungere questo obiettivo?


1
Solo per farvi sapere che ho trovato questo modo più conveniente (il timeout di chiamata non funziona bene se ci sono problemi di rete, almeno per me): blog.golang.org/context
Audrius

@ Audrius Qualche idea sul perché il timeout di chiamata non funziona quando ci sono problemi di rete? Penso di vedere la stessa cosa. Pensavo fosse a questo che servisse DialTimeout?!?!
Giordania

@Jordan Difficile da dire perché non mi sono immerso così in profondità nel codice della libreria. Ho pubblicato la mia soluzione di seguito. Lo sto usando in produzione da un po 'di tempo e finora "funziona" (tm).
Audrius

Risposte:


255

Apparentemente in Go 1.3 http.Client ha il campo Timeout

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

Questo ha fatto il trucco per me.


10
Bene, per me va bene così. Sono contento di aver fatto scorrere un po 'verso il basso :)
James Adam

5
C'è un modo per avere un timeout diverso per richiesta?
Arnaud Rinquin

11
Cosa succede quando scade il timeout? Non Getrestituire un errore? Sono un po 'confuso perché il Godoc per Clientdice: Il timer rimane in esecuzione dopo il ritorno di Get, Head, Post o Do e interromperà la lettura di Response.Body. Quindi vuol dire che sia Get o la lettura Response.Bodypotrebbe essere interrotta da un errore?
Avi Flax

1
Domanda, qual è la differenza tra http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Roy Lee

2
@Roylee Una delle principali differenze secondo i documenti: http.Client.Timeoutinclude il tempo per leggere il corpo della risposta, http.Transport.ResponseHeaderTimeoutnon lo include.
imwill

53

È necessario impostare il proprio client con il proprio trasporto che utilizza una funzione di composizione personalizzata che avvolge DialTimeout .

Qualcosa di simile (completamente non testato ) questo :

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

Molte grazie! Questa è esattamente la cosa che stavo cercando.
pymd

qual è il vantaggio di utilizzare net.DialTimeout rispetto al Transport.ResponseHeaderTimeout descritto dalla risposta di zzzz?
Daniele B

4
@ Daniel B: Stai facendo la domanda sbagliata. Non si tratta di vantaggi, ma di ciò che fa ogni codice. DialTimeouts salta in se il server non può essere dailed mentre altri timeout si attivano se alcune operazioni sulla connessione stabilita richiedono troppo tempo. Se i tuoi server di destinazione stabiliscono una connessione rapidamente ma poi iniziano a bloccarti lentamente, un timeout di chiamata non aiuta.
Volker

1
@ Volker, grazie per la tua risposta. In realtà l'ho capito anche io: sembra che Transport.ResponseHeaderTimeout imposta il timeout di lettura, cioè il timeout dopo che la connessione è stata stabilita, mentre il tuo è un timeout di chiamata. La soluzione di dmichael si occupa sia del timeout del quadrante che del timeout di lettura.
Daniele B

1
@ Jonno: non ci sono cast in Go. Queste sono conversioni di tipo.
Volker

31

Per aggiungere alla risposta di Volker, se desideri impostare anche il timeout di lettura / scrittura oltre al timeout di connessione, puoi fare qualcosa di simile al seguente

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

Questo codice è testato e funziona in produzione. L'essenza completa con i test è disponibile qui https://gist.github.com/dmichael/5710968

Tieni presente che dovrai creare un nuovo client per ogni richiesta a causa del conn.SetDeadlinequale fa riferimento a un punto in futurotime.Now()


Non dovresti controllare il valore di ritorno di conn.SetDeadline?
Eric Urban

3
Questo timeout non funziona con le connessioni keepalive, che è l'impostazione predefinita e ciò che la maggior parte delle persone dovrebbe usare immagino. Ecco cosa mi è venuto in mente per affrontare questo: gist.github.com/seantalts/11266762
xitrium

Grazie @xitrium ed Eric per il contributo aggiuntivo.
dmichael

Mi sembra che non sia come hai detto che avremo bisogno di creare un nuovo cliente per ogni richiesta. Poiché Dial è una funzione che penso venga chiamata ogni volta che invii nuovamente ciascuna richiesta nello stesso client.
A-letubby

Sei sicuro di aver bisogno ogni volta di un nuovo cliente? Ogni volta che effettua la composizione, invece di usare net.Dial, userà la funzione che TimeoutDialer costruisce. Questa è una nuova connessione, con la scadenza valutata ogni volta, da una nuova chiamata time.Now ().
Blake Caldwell

16

Se vuoi farlo per richiesta, la gestione degli errori ignorata per brevità:

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
Informazioni extra: per doc, il termine imposto da Context prevede anche la lettura del Body, analogamente al http.Client.Timeout.
kubanczyk

1
Dovrebbe essere una risposta accettata per Go 1.7+. Per Go 1.13+ può essere leggermente accorciato usando NewRequestWithContext
kubanczyk

9

Un modo veloce e sporco:

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

Questo sta mutando lo stato globale senza alcun coordinamento. Eppure potrebbe essere ok per il tuo fetcher di URL. Altrimenti crea un'istanza privata di 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)
...

Niente di cui sopra è stato testato.


Per favore, qualcuno mi corregga, ma sembra che ResponseHeaderTimeout riguardi il timeout di lettura, ovvero il timeout dopo che la connessione è stata stabilita. La soluzione più completa sembra essere quella di @dmichael, in quanto permette di impostare sia il timeout del dial che il timeout di lettura.
Daniele B

http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45aiutami molto nel test di scrittura per il timeout della richiesta. Grazie mille.
Lee


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

Questo può essere d'aiuto, ma nota che si ResponseHeaderTimeoutavvia solo dopo aver stabilito la connessione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.