Cookies management by TermsFeed Cookie Consent

โฑ๏ธ Set HTTP client timeout in Go

http

An HTTP client timeout is a time limit in which the server must process the request and return the response. If the set timeout is exceeded, the HTTP client should cancel the request and report an error. This mechanism is very important in applications that run in a production environment. It helps prevent the application from getting stuck waiting for a server response, which may take a long time or not happen at all.

In Go, the standard HTTP client does not set timeouts by default. It means that an app can wait for the server’s response forever. To prevent this, you should always remember to set a timeout in your HTTP client. And this is what we want to do here ๐Ÿ™‚.

There are three scenarios of setting a timeout for HTTP requests that we are going to cover in this article:

Set timeout per HTTP client

If you want to set the same timeout for all requests of a new http.Client, initialize the client with the Timeout field set, where you specify the time limit in which the request must be processed.

Look at the example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
    "log"
    "net/http"
    "time"
)

func timeout(w http.ResponseWriter, req *http.Request) {
    time.Sleep(5 * time.Second)
}

func server() {
    go func() {
        http.HandleFunc("/timeout", timeout)
        log.Fatal(http.ListenAndServe(":8090", nil))
    }()
}

func main() {
    server()

    httpClient := http.Client{Timeout: 2 * time.Second}
    if _, err := httpClient.Get("http://localhost:8090/timeout"); err != nil {
        log.Fatal(err)
    }
}

In lines 9-18, we declare a new simple server whose only task is to pause work for 5 seconds by calling the time.Sleep function. In the first line of the main() function, we start this server. In the remaining lines, we create a new http.Client with the Timeout field set to 2 seconds and send a new request to the created server.

So by sending a request with a 2-second timeout to a server that pauses for 5 seconds, we will get a timeout error:

Output:

2022/08/08 12:31:43 Get "http://localhost:8090/timeout": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
exit status 1

If you run the app without the Timeout set, you will not get any error.

Set timeout per request

If you want to set a timeout for an individual request, create a new request Context using the context.WithTimeout() function. Then, create a new request with this context as an argument using the http.NewRequestWithContext() constructor.

package main

import (
    "context"
    "log"
    "net/http"
    "time"
)

// func server() {...} the same as in the first example...

func main() {
    server()

    httpClient := http.Client{}

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8090/timeout", nil)
    if err != nil {
        log.Fatal(err)
    }

    if _, err := httpClient.Do(req); err != nil {
        log.Fatal(err)
    }
}

The context.Context is an object that is a common Go concept used by HTTP clients and servers to send/receive request-scoped values, cancellation, and deadline signals to deeper layers of services. In our case, the context is used to set the deadline for getting a response for a given new request.

Note that in addition to the timeout value, the context.WithTimeout() function also takes a parent context as an argument, which in our example is a new empty context created with the context.Background() function. As a result, we receive the context and the cancellation function to release resources associated with it, which we call as a deferred function cancel().

Since as in the previous example, we send a request with a 2-second timeout to the server that pauses for 5 seconds, we get a similar error message:

Output:

2022/08/08 15:06:18 Get "http://localhost:8090/timeout": context deadline exceeded
exit status 1

Set timeout for default HTTP client

Sometimes you can use code that uses the default HTTP client: http.DefaultClient. Often there is no way to change this code so that it uses the custom-defined HTTP client, e.g. in external packages or in code where backward compatibility must be maintained. Fortunately, this does not mean that we cannot define a timeout for such a client. The example below shows how to do it.

package main

import (
    "log"
    "net/http"
    "time"
)

// func server() {...} the same as in the first example...

func main() {
    server()

    http.DefaultClient.Timeout = 2 * time.Second

    if _, err := http.Get("http://localhost:8090/timeout"); err != nil {
        log.Fatal(err)
    }
}

All you have to do is set the Timeout field in the default HTTP client, and then all functions of the http package using the default client will respect this timeout.

In this example, you get the same error message as if you set the timeout per HTTP client:

Output:

2022/08/09 09:48:58 Get "http://localhost:8090/timeout": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
exit status 1

Thank you for being on our site ๐Ÿ˜Š. If you like our tutorials and examples, please consider supporting us with a cup of coffee and we'll turn it into more great Go examples.

Have a great day!

Buy Me A Coffee

โฐ Handle HTTP timeout error in Go

shorts http

๐Ÿœ Print HTTP request/response for debugging in Go

Learn how to pretty print HTTP request and response
http

๐Ÿงช Write end-to-end tests in Go using httptest.Server

shorts httptest http testing