จะตั้งค่าการหมดเวลาสำหรับคำขอ http get () ใน Golang ได้อย่างไร?


107

ฉันกำลังสร้าง URL fetcher ใน Go และมีรายการ URL ที่จะดึงข้อมูล ฉันส่งhttp.Get()คำขอไปยังแต่ละ URL และได้รับคำตอบ

resp,fetch_err := http.Get(url)

ฉันจะกำหนดระยะหมดเวลาที่กำหนดเองสำหรับคำขอรับแต่ละรายการได้อย่างไร (เวลาเริ่มต้นนั้นนานมากและทำให้ fetcher ของฉันช้ามาก) ฉันต้องการให้ตัวดึงข้อมูลของฉันมีระยะหมดเวลาประมาณ 40-45 วินาทีหลังจากนั้นควรส่งคืน "คำขอหมดเวลา" และไปยัง URL ถัดไป

ฉันจะบรรลุเป้าหมายนี้ได้อย่างไร?


1
เพียงแค่แจ้งให้พวกคุณทราบว่าฉันพบวิธีนี้สะดวกกว่า (การหมดเวลาการหมุนหมายเลขทำงานได้ไม่ดีหากมีปัญหาเครือข่ายอย่างน้อยสำหรับฉัน): blog.golang.org/context
Audrius

@Audrius มีความคิดอย่างไรว่าทำไมการหมดเวลาของการโทรไม่ทำงานเมื่อมีปัญหาเครือข่าย ฉันคิดว่าฉันกำลังเห็นสิ่งเดียวกัน ฉันคิดว่านั่นคือ DialTimeout เพื่ออะไร!?!
จอร์แดน

@ จอร์แดนยากที่จะบอกเพราะฉันไม่ได้ดำลึกลงไปในรหัสห้องสมุด ฉันได้โพสต์วิธีแก้ปัญหาไว้ด้านล่างแล้ว ฉันใช้มันในการผลิตมาระยะหนึ่งแล้วและจนถึงตอนนี้มัน "ใช้ได้" (tm)
Audrius

คำตอบ:


257

เห็นได้ชัดใน Go 1.3 http ไคลเอนต์มีฟิลด์หมดเวลา

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

นั่นเป็นเคล็ดลับสำหรับฉัน


10
นั่นก็ดีพอสำหรับฉัน ดีใจที่เลื่อนลงมาหน่อย :)
James Adam

5
มีวิธีกำหนดระยะหมดเวลาที่แตกต่างกันตามคำขอหรือไม่
Arnaud Rinquin

11
จะเกิดอะไรขึ้นเมื่อหมดเวลา ไม่Getกลับข้อผิดพลาด? ฉันสับสนเล็กน้อยเพราะ Godoc Clientพูดว่า: ตัวจับเวลายังคงทำงานหลังจาก Get, Head, Post หรือ Do return และจะขัดจังหวะการอ่าน Response.Body นั่นหมายความว่าอย่างใดอย่างหนึ่ง Get หรือการอ่านResponse.Bodyอาจถูกขัดจังหวะด้วยข้อผิดพลาด?
Avi Flax

1
คำถามอะไรคือความแตกต่างระหว่างhttp.Client.Timeoutกับhttp.Transport.ResponseHeaderTimeout?
Roy Lee

2
@Roylee ความแตกต่างหลักประการหนึ่งตามเอกสาร: http.Client.Timeoutรวมเวลาในการอ่านเนื้อหาตอบกลับhttp.Transport.ResponseHeaderTimeoutแต่ไม่รวมไว้ด้วย
60

53

คุณจำเป็นต้องตั้งค่าของคุณเองไคลเอ็นต์กับของคุณเองการขนส่งที่ใช้ฟังก์ชั่นแบบ Dial ที่กำหนดเองซึ่งล้อมรอบDialTimeout

สิ่งที่ต้องการ ( ยังไม่ทดสอบอย่างสมบูรณ์) สิ่งนี้ :

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

ขอบคุณมาก! นี่คือสิ่งที่ฉันกำลังมองหา
pymd

ข้อดีของการใช้ net.DialTimeout ผ่าน Transport.ResponseHeaderTimeout ที่อธิบายโดยคำตอบของ zzzz คืออะไร?
Daniele B

4
@ Daniel B: คุณกำลังถามคำถามผิด ไม่เกี่ยวกับข้อดี แต่เกี่ยวกับการทำงานของแต่ละรหัส DialTimeouts จะกระโดดเข้ามาหากเซิร์ฟเวอร์ไม่สามารถดำเนินการได้ในขณะที่การหมดเวลาอื่นเริ่มต้นหากการดำเนินการบางอย่างในการเชื่อมต่อที่กำหนดใช้เวลานานเกินไป หากเซิร์ฟเวอร์เป้าหมายของคุณสร้างการเชื่อมต่ออย่างรวดเร็ว แต่เริ่มแบนช้าคุณจะหมดเวลาการโทรจะไม่ช่วย
Volker

1
@Volker ขอบคุณสำหรับคำตอบ อันที่จริงฉันก็รู้เช่นกันดูเหมือนว่า Transport.ResponseHeaderTimeout จะตั้งค่าการหมดเวลาการอ่านนั่นคือการหมดเวลาหลังจากสร้างการเชื่อมต่อแล้วในขณะที่คุณกำลังหมดเวลาการหมุนหมายเลข โซลูชันโดย dmichael เกี่ยวข้องกับการหมดเวลาการโทรและการหมดเวลาการอ่าน
Daniele B

1
@ จอนโน: ไม่มีแคสในโก นี่คือการแปลงประเภท
Volker

31

หากต้องการเพิ่มคำตอบของ Volker หากคุณต้องการตั้งค่าการหมดเวลาอ่าน / เขียนนอกเหนือจากการหมดเวลาการเชื่อมต่อคุณสามารถทำสิ่งต่อไปนี้ได้

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

รหัสนี้ได้รับการทดสอบและใช้งานได้ในการผลิต ดูข้อมูลสำคัญทั้งหมดพร้อมการทดสอบได้ที่นี่ https://gist.github.com/dmichael/5710968

โปรดทราบว่าคุณจะต้องสร้างไคลเอนต์ใหม่สำหรับแต่ละคำขอเนื่องจากการconn.SetDeadlineอ้างอิงถึงประเด็นในอนาคตtime.Now()


คุณไม่ควรตรวจสอบค่าส่งคืนของ conn.SetDeadline?
Eric Urban

3
การหมดเวลานี้ใช้ไม่ได้กับการเชื่อมต่อแบบคงที่ซึ่งเป็นค่าเริ่มต้นและสิ่งที่คนส่วนใหญ่ควรใช้ฉันคิดว่า นี่คือสิ่งที่ฉันคิดขึ้นเพื่อจัดการกับสิ่งนี้: gist.github.com/seantalts/11266762
xitrium

ขอบคุณ @xitrium และ Eric สำหรับข้อมูลเพิ่มเติม
dmichael

ฉันรู้สึกว่าไม่ใช่อย่างที่คุณบอกว่าเราจะต้องสร้างลูกค้าใหม่สำหรับแต่ละคำขอ เนื่องจาก Dial เป็นฟังก์ชันที่ฉันคิดว่ามันได้รับการโทรทุกครั้งที่คุณส่งคำขอแต่ละรายการในไคลเอนต์เดียวกัน
A-letubby

คุณแน่ใจว่าต้องการลูกค้าใหม่ทุกครั้ง? ทุกครั้งที่โทรออกแทนที่จะใช้ net.Dial จะใช้ฟังก์ชันที่ TimeoutDialer สร้างขึ้น นั่นคือการเชื่อมต่อใหม่ที่มีการประเมินกำหนดเวลาทุกครั้งจากเวลาใหม่ตอนนี้ () โทร
Blake Caldwell

16

หากคุณต้องการทำตามคำขอการจัดการที่ผิดพลาดจะถูกละเว้นเพื่อความกะทัดรัด:

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
ข้อมูลเพิ่มเติม: ตามเอกสารวันครบกำหนดที่กำหนดโดยบริบทรวมถึงการอ่านเนื้อหาเช่นเดียวกับไฟล์http.Client.Timeout.
kubanczyk

2
ควรเป็นคำตอบที่ยอมรับสำหรับ Go 1.7+ สำหรับ Go 1.13+ สามารถย่อให้สั้นลงเล็กน้อยโดยใช้NewRequestWithContext
kubanczyk

9

วิธีที่รวดเร็วและสกปรก:

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

นี่เป็นการกลายพันธุ์ของรัฐทั่วโลกโดยไม่มีการประสานงานใด ๆ แต่มันอาจจะโอเคสำหรับ url fetcher ของคุณ มิฉะนั้นให้สร้างอินสแตนซ์ส่วนตัวของ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)
...

ไม่มีการทดสอบข้างต้น


โปรดทุกคนช่วยแก้ไขให้ฉัน แต่ดูเหมือนว่า ResponseHeaderTimeout จะเกี่ยวกับการหมดเวลาการอ่านนั่นคือการหมดเวลาหลังจากสร้างการเชื่อมต่อแล้ว โซลูชันที่ครอบคลุมที่สุดน่าจะเป็นของ @dmichael เนื่องจากสามารถตั้งค่าการหมดเวลาการหมุนหมายเลขและการหมดเวลาการอ่านได้
Daniele B

http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45ช่วยฉันมากในการทดสอบการเขียนสำหรับการขอหมดเวลา ขอบคุณมาก.
lee


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

สิ่งนี้อาจช่วยได้ แต่สังเกตว่าResponseHeaderTimeoutเริ่มต้นหลังจากสร้างการเชื่อมต่อแล้วเท่านั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.