175

I'm making a URL fetcher in Go and have a list of URLs to fetch. I send http.Get() requests to each URL and obtain their response.

resp,fetch_err := http.Get(url)

How can I set a custom timeout for each Get request? (The default time is very long and that makes my fetcher really slow.) I want my fetcher to have a timeout of around 40-45 seconds after which it should return "request timed out" and move on to the next URL.

How can I achieve this?

pymd
  • 4,021
  • 6
  • 26
  • 27
  • 1
    Just letting you guys know that I found this way more convenient (the dial timeout does not work well if there are network issues, at least for me): https://blog.golang.org/context – Audrius Jun 19 '15 at 19:10
  • @Audrius Any idea why the dial timeout doesn't work when there are network issues? I think I'm seeing the same thing. I thought that's what DialTimeout was for?!?! – Jordan Aug 18 '15 at 17:06
  • @Jordan Hard to tell as I did not dive that deep into the library code. I have posted my solution below. I'm using it in production for quite a while now and so far it "just works"(tm). – Audrius Aug 19 '15 at 19:27

7 Answers7

372

Apparently in Go 1.3 http.Client has Timeout field

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

That's done the trick for me.

kubanczyk
  • 5,184
  • 1
  • 41
  • 52
sparrovv
  • 6,894
  • 2
  • 29
  • 34
  • 12
    Well, that's good enough for me. Glad I scrolled down a bit :) – James Adam Sep 15 '14 at 00:10
  • 10
    Is there a way to have a different timeout per request? – Arnaud Rinquin Jun 23 '15 at 13:40
  • 18
    What happens when the timeout hits? Does `Get` return an error? I’m a little confused because the Godoc for `Client` says: _The timer remains running after Get, Head, Post, or Do return and will interrupt reading of the Response.Body._ So does that mean that _either_ `Get` _or_ reading `Response.Body` could be interrupted by an error? – Avi Flax Jul 27 '15 at 22:41
  • Yes, it means that they can be interrupted. You'll see `net/http: request canceled (Client.Timeout exceeded while reading body)`. A way around this is to define your own transport or use something like https://github.com/mreiferson/go-httpclient – Kyle Chadha Nov 25 '15 at 15:20
  • 3
    Question, what's the difference between `http.Client.Timeout` vs. `http.Transport.ResponseHeaderTimeout` ? – Roy Lee Aug 16 '16 at 10:19
  • 4
    @Roylee One of the main differences according to the docs: `http.Client.Timeout` includes the time to read the response body, `http.Transport.ResponseHeaderTimeout` doesn't include it. – imwill May 24 '17 at 21:01
  • What if I then need to make request with that client with longer timeout? https://stackoverflow.com/questions/75741987/how-to-override-the-default-timeout-of-an-http-client-in-go-for-specific-request – joeyave Mar 17 '23 at 11:25
56

You need to set up your own Client with your own Transport which uses a custom Dial function which wraps around DialTimeout.

Something like (completely untested) this:

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
  • 40,468
  • 7
  • 81
  • 87
  • Thanks a lot! This is exactly the thing I was looking for. – pymd Jun 03 '13 at 12:17
  • what's the advantage of using the net.DialTimeout over the Transport.ResponseHeaderTimeout described by the zzzz's answer? – Daniele B Oct 13 '13 at 13:55
  • 4
    @Daniel B: You are asking the wrong question. It is not about advantages but about what each code does. DialTimeouts jump in if the server cannot be dailed while other timeouts kick in if certain operations on the established connection take too long. If your target servers establish a connection quickly but then start to slow-ban you a dial timeout won't help. – Volker Oct 13 '13 at 20:12
  • 1
    @Volker, thanks for your answer. Actually I realized it as well: it looks like the Transport.ResponseHeaderTimeout sets the read timeout, that is the timeout after the connection has been established, while your is a dial timeout. The solution by dmichael deals with both dial timeout and read timeout. – Daniele B Oct 14 '13 at 13:10
  • not enough character to warrant an "edit": closing parenthesis need to move `time.Duration(2 * time.Second)` --> `time.Duration(2) * time.Second` – Jonno Nov 26 '13 at 22:13
  • @Jonno: Why do you think so? – Volker Nov 26 '13 at 23:01
  • @Volker - my apologies - this is only true if the '2' in your example is a variable. I tried this: `a := 2` followed by `a * time.Second`. the go compiler complains: `invalid operation: a * time.Second (mismatched types int and time.Duration)`. in other words, you can't multiply int by duration - have to cast int to time.duration first. Said another way your example could be without the time.Duration casting - `var timeout = 2 * time.Second` i.e. the compiler does the necessary magic. BTW as reference my go version is go1.1.2 windows/amd64. – Jonno Nov 27 '13 at 01:04
  • 2
    @Jonno: There are no casts in Go. These are type conversions. – Volker Nov 27 '13 at 06:09
49

If you want to do it per request, err handling ignored for brevity:

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)
Mihai Todor
  • 8,014
  • 9
  • 49
  • 86
Chad Grant
  • 44,326
  • 9
  • 65
  • 80
  • 2
    Extra info: per doc, the deadline imposed by Context encompasses also reading the Body, similarly to the `http.Client.Timeout`. – kubanczyk Oct 04 '19 at 07:31
  • 5
    Should be an **accepted** answer for Go 1.7+. For Go 1.13+ can be slightly shortened using [NewRequestWithContext](https://golang.org/pkg/net/http/#NewRequestWithContext) – kubanczyk Oct 04 '19 at 07:47
36

To add to Volker's answer, if you would also like to set the read/write timeout in addition to the connect timeout you can do something like the following

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

This code is tested and is working in production. The full gist with tests is available here https://gist.github.com/dmichael/5710968

Be aware that you will need to create a new client for each request because of the conn.SetDeadline which references a point in the future from time.Now()

dmichael
  • 648
  • 6
  • 7
  • Shouldn't you check the return value of conn.SetDeadline? – Eric Urban Mar 02 '14 at 21:58
  • 3
    This timeout doesn't work with keepalive connections, which is the default and what most people should be using I imagine. Here's what I came up with to deal with this: http://gist.github.com/seantalts/11266762 – xitrium Apr 24 '14 at 21:52
  • Thanks @xitrium and Eric for the additional input. – dmichael May 07 '14 at 03:03
  • I feel like it is not as you said that we will need to create a new client for each request. Since Dial is a function which I think it gets call every time again you send each request in the same client. – A-letubby Jul 06 '14 at 04:33
  • You sure you need a new client each time? Each time it's dialing, instead of using net.Dial, it'll use the function that TimeoutDialer builds. That's a new connection, with the deadline evaluated each time, from a fresh time.Now() call. – Blake Caldwell Jan 30 '15 at 06:05
16

There are several client-side timeouts in the Go http module, and there are some samples of those timeouts on current answers.

Here is one image to illustrate the client-side timeout refer to The complete guide to Go net/http timeouts enter image description here

There are two methods to set the timeout for HTTP request

  • http.Client
client := http.Client{
    Timeout: 3 * time.Second,
}
resp, err := client.Do(req)
  • Context
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, URL)

The difference between them is

  • Using context is request specific while using the Client timeout might be applied to all request pass to Do method client has.
  • If you want to specialize your deadline/timeout to each request then use context, otherwise, if you want 1 timeout for every outbound request then using client timeout is enough.
zangw
  • 43,869
  • 19
  • 177
  • 214
12

A quick and dirty way:

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

This is mutating global state w/o any coordination. Yet it might be possibly okay for your url fetcher. Otherwise create a private instance of 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)
...

Nothing above was tested.

zzzz
  • 87,403
  • 16
  • 175
  • 139
  • Please anyone correct me, but it looks like the ResponseHeaderTimeout is about the read timeout, that is the timeout after the connection has been established. The most comprehensive solution seems to be the one by @dmichael, as it allows to set both the dial timeout and the read timeout. – Daniele B Oct 13 '13 at 15:10
  • `http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45` help me alot in write test for request timeout. Thank you very much. – lee Sep 19 '19 at 14:59
0
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

This may help, but notice that ResponseHeaderTimeout starts only after the connection is established.

T.Max
  • 11