Bagaimana cara mengatur waktu tunggu untuk permintaan http.Get () di Golang?

106

Saya membuat pengambil URL di Go dan memiliki daftar URL untuk diambil. Saya mengirim http.Get()permintaan ke setiap URL dan mendapatkan tanggapan mereka.

resp,fetch_err := http.Get(url)

Bagaimana cara mengatur waktu tunggu khusus untuk setiap permintaan Dapatkan? (Waktu default sangat lama dan itu membuat fetcher saya sangat lambat.) Saya ingin fetcher saya memiliki waktu tunggu sekitar 40-45 detik, setelah itu harus mengembalikan "waktu permintaan habis" dan beralih ke URL berikutnya.

Bagaimana saya bisa mencapai ini?

pymd
sumber
1
Sekadar memberi tahu kalian bahwa saya merasa cara ini lebih nyaman (batas waktu panggilan tidak berfungsi dengan baik jika ada masalah jaringan, setidaknya untuk saya): blog.golang.org/context
Audrius
@ Audrius Ada ide mengapa batas waktu panggilan tidak berfungsi saat ada masalah jaringan? Saya pikir saya melihat hal yang sama. Saya pikir untuk itulah DialTimeout?!?!
Yordania
@ Jordan Sulit untuk mengatakan karena saya tidak menyelam jauh ke dalam kode perpustakaan. Saya telah memposting solusi saya di bawah ini. Saya menggunakannya dalam produksi cukup lama sekarang dan sejauh ini "hanya berfungsi" (tm).
Audrius

Jawaban:

255

Ternyata di Go 1.3 http.Client memiliki kolom Timeout

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

Itu trik untuk saya.

sparrovv.dll
sumber
10
Nah, itu cukup bagus untukku. Senang saya menggulir ke bawah sedikit :)
James Adam
5
Adakah cara untuk memiliki waktu tunggu yang berbeda untuk setiap permintaan?
Arnaud Rinquin
11
Apa yang terjadi jika batas waktu tercapai? Apakah Getmengembalikan kesalahan? Saya agak bingung karena Godoc for Clientmengatakan: Timer tetap berjalan setelah Get, Head, Post, atau Do kembali dan akan mengganggu pembacaan Response.Body. Jadi apakah itu berarti bahwa salah satu Get atau pembacaan Response.Bodydapat terganggu oleh kesalahan?
Avi Flax
1
Pertanyaan, apa perbedaan antara http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Roy Lee
2
@Roylee Salah satu perbedaan utama menurut dokumen: http.Client.Timeoutmenyertakan waktu untuk membaca isi respons, http.Transport.ResponseHeaderTimeouttidak memasukkannya.
Imwill
53

Anda perlu menyiapkan Klien Anda sendiri dengan Transport Anda sendiri yang menggunakan fungsi Dial kustom yang melingkupi DialTimeout .

Sesuatu seperti (sepenuhnya belum teruji ) ini :

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
sumber
Terima kasih banyak! Ini persis seperti yang saya cari.
pymd
apa keuntungan menggunakan net.DialTimeout dibandingkan Transport.ResponseHeaderTimeout yang dijelaskan oleh jawaban zzzz?
Daniele B
4
@ Daniel B: Anda menanyakan pertanyaan yang salah. Ini bukan tentang keuntungan tetapi tentang apa yang dilakukan setiap kode. DialTimeout masuk jika server tidak dapat dihubungi sementara waktu tunggu lainnya masuk jika operasi tertentu pada koneksi yang dibuat membutuhkan waktu terlalu lama. Jika server target Anda membuat koneksi dengan cepat tetapi kemudian mulai melarang Anda lambat, batas waktu panggilan tidak akan membantu.
Volker
1
@Volker, terima kasih atas jawaban Anda. Sebenarnya saya juga menyadarinya: sepertinya Transport.ResponseHeaderTimeout menetapkan batas waktu baca, yaitu batas waktu setelah koneksi dibuat, sedangkan Anda adalah batas waktu panggilan. Solusi oleh dmichael berkaitan dengan batas waktu dial dan batas waktu baca.
Daniele B
1
@ Jonno: Tidak ada pemain di Go. Ini adalah jenis konversi.
Volker
31

Untuk menambah jawaban Volker, jika Anda juga ingin mengatur waktu tunggu baca / tulis selain waktu tunggu koneksi, Anda dapat melakukan hal seperti berikut ini

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

Kode ini telah diuji dan berfungsi dalam produksi. Inti lengkap dengan tes tersedia di sini https://gist.github.com/dmichael/5710968

Ketahuilah bahwa Anda perlu membuat klien baru untuk setiap permintaan karena dari conn.SetDeadlinemana merujuk suatu titik di masa mendatangtime.Now()

dmichael
sumber
Tidakkah sebaiknya Anda memeriksa nilai kembalian conn.SetDeadline?
Eric Urban
3
Batas waktu ini tidak berfungsi dengan koneksi keepalive, yang merupakan default dan apa yang seharusnya digunakan kebanyakan orang, saya bayangkan. Inilah yang saya pikirkan untuk menangani ini: gist.github.com/seantalts/11266762
xitrium
Terima kasih @xitrium dan Eric atas masukan tambahannya.
dmichael
Saya merasa bukan seperti yang Anda katakan bahwa kami perlu membuat klien baru untuk setiap permintaan. Karena Dial adalah fungsi yang menurut saya mendapat panggilan setiap kali Anda mengirim setiap permintaan di klien yang sama.
A-letubby
Anda yakin Anda membutuhkan klien baru setiap saat? Setiap kali melakukan panggilan, alih-alih menggunakan net.Dial, itu akan menggunakan fungsi yang dibuat oleh TimeoutDialer. Itu koneksi baru, dengan tenggat waktu dievaluasi setiap kali, dari panggilan waktu baru.Now ().
Blake Caldwell
16

Jika Anda ingin melakukannya sesuai permintaan, penanganan yang salah diabaikan agar singkatnya:

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
sumber
1
Info tambahan: per dokumen, batas waktu yang diberlakukan oleh Konteks mencakup juga membaca Body, mirip dengan http.Client.Timeout.
kubanczyk
1
Harus menjadi jawaban yang diterima untuk Go 1.7+. Untuk Go 1.13+ dapat sedikit dipersingkat menggunakan NewRequestWithContext
kubanczyk
9

Cara cepat dan kotor:

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

Ini mutasi keadaan global tanpa koordinasi apapun. Namun mungkin tidak masalah bagi pengambil url Anda. Jika tidak, buat instance pribadi dari 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)
...

Tidak ada hal di atas yang diuji.

zzzz
sumber
Tolong ada yang mengoreksi saya, tetapi sepertinya ResponseHeaderTimeout adalah tentang batas waktu baca, yaitu batas waktu setelah koneksi dibuat. Solusi paling komprehensif tampaknya adalah solusi oleh @dmichael, karena memungkinkan untuk menyetel batas waktu panggilan dan batas waktu baca.
Daniele B
http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45banyak membantu saya dalam tes tulis untuk batas waktu permintaan. Terima kasih banyak.
lee
0

Anda dapat menggunakan https://github.com/franela/goreq yang menangani batas waktu dengan cara dan cara yang sederhana.

marcosnils
sumber
Perpustakaan ini tampak luar biasa!
ChrisR
-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Ini mungkin membantu, tetapi perhatikan itu ResponseHeaderTimeoutdimulai hanya setelah koneksi dibuat.

T.Max
sumber