如何在Golang中为http.Get()请求设置超时?


106

我在Go中制作了一个URL提取程序,并具有要提取的URL列表。我将http.Get()请求发送到每个URL并获得他们的响应。

resp,fetch_err := http.Get(url)

如何为每个Get请求设置自定义超时?(默认时间很长,这会使我的提取程序非常慢。)我希望提取程序的超时时间为40-45秒左右,之后它应该返回“请求超时”并移至下一个URL。

我该如何实现?


1
只是让大家知道,我发现这种方式更方便(至少在网络问题上,拨号超时无法正常工作,至少对我而言):blog.golang.org/context
Audrius

@Audrius知道网络有问题时为什么拨号超时不起作用吗?我想我也看到了同样的事情。我以为那是DialTimeout的目的?!?!
乔丹

@Jordan很难说,因为我没有深入研究库代码。我已经在下面发布了我的解决方案。我已经在生产中使用了很长时间,到目前为止,它“正常工作”(tm)。
奥德里斯·

Answers:


255

显然在Go 1.3中,http.Client具有“超时”字段

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

这为我完成了窍门。


10
好吧,对我来说已经足够了。很高兴我向下滚动了一点:)
詹姆斯·亚当

5
有没有一种方法可以使每个请求的超时时间不同?
Arnaud Rinquin

10
超时到时该怎么办?是否Get返回错误?我有些困惑,因为Godoc for Client表示:计时器在Get,Head,Post或Do返回后仍保持运行,并且会中断Response.Body的读取。这是否意味着这两种 Get 读数Response.Body可能是由错误而中断?
Avi Flax

1
问题,http.Client.Timeoutvs和有http.Transport.ResponseHeaderTimeout什么区别?
罗伊·李

2
@Roylee根据文档的主要区别之一:http.Client.Timeout包括读取响应正文的时间,http.Transport.ResponseHeaderTimeout不包括它。
imwill

53

您需要使用自己的传输器设置自己的客户端,该传输器使用环绕DialTimeout的自定义Dial函数。

像(完全未经测试这样的东西:

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 2013年

zzzz的答案描述的使用net.DialTimeout而不是Transport.ResponseHeaderTimeout有什么优势?
Daniele B

4
@Daniel B:您问的是错误的问题。这与优势无关,而与每个代码的作用有关。如果无法建立服务器,则DialTimeouts会跳入,而如果建立的连接上的某些操作花费太长时间,则其他超时会跳入。如果目标服务器快速建立连接,但随后开始缓慢禁止拨号超时,则无济于事。
Volker

1
@Volker,感谢您的回答。实际上,我也意识到了这一点:它看起来像Transport.ResponseHeaderTimeout设置读取超时,即建立连接后的超时,而您是拨号超时。dmichael的解决方案处理拨号超时和读取超时。
Daniele B

1
@Jonno:Go中没有演员表。这些是类型转换。
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
此超时不适用于keepalive连接,这是默认设置,我想这是大多数人应该使用的连接。这是我想出的解决方案:gist.github.com/seantalts/11266762
xitrium

感谢@xitrium和Eric的额外投入。
2014年

我感觉并非如您所说,我们将需要为每个请求创建一个新客户端。由于Dial是一项功能,我认为您每次在同一客户端中发送每个请求时都会再次调用它。
2014年

您确定每次都需要一个新客户吗?每次拨号时,它将使用TimeoutDialer构建的功能,而不是使用net.Dial。这是一个新的连接,从新的time.Now()调用开始,每次都评估了截止日期。
布莱克·考德威尔

16

如果要根据请求执行此操作,为简便起见,忽略了err处理:

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
额外信息:每个文档,Context规定的截止日期还包括阅读正文,与相似http.Client.Timeout
kubanczyk

1
应该是Go 1.7+ 的可接受答案。对于Go 1.13+,可以使用NewRequestWithContext
kubanczyk

9

快速而肮脏的方式:

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

这正在改变全局状态而没有任何协调。但是,对于您的网址提取程序来说可能还可以。否则,创建一个私有实例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帮助我大量编写请求超时测试。非常感谢你。


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

这可能会有所帮助,但请注意,ResponseHeaderTimeout只有在建立连接后才能开始。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.