How to set golang HTTP client timeout? [SOLVED]


GO

Author: Tuan Nguyen
Reviewer: Deepak Prasad

Introduction

In this tutorial, we will explain some methods to set timeout for HTTP requests. A Request Timeout header is defined for Hypertext Transfer Protocol (HTTP). This end-to-end header informs an origin server and any intermediaries of the maximum time that a client will await a response to its request. A server can use this header to ensure that a timely response is generated. This also identifies requests as being potentially long-lived and allows for better resource allocation for these requests.

The image below shows multiple phases of sending requests, we can set the timeout for some specific phases:

How to set golang HTTP client timeout? [SOLVED]

 

Building a simple HTTP server (Lab Setup)

If you do not know how to build an HTTP server in Golang, you can visit our post which explains how we can build a simple HTTP server and client in Golang. For demo purposes, in this example, we will have a function that handles all the requests. This function will sleep 5 seconds before returning a message to the client:

package main

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

func main() {
	http.HandleFunc("/", TimeoutFunc)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatalf("Error creating server %s\n", err.Error())
	}
}
func TimeoutFunc(w http.ResponseWriter, r *http.Request) {
	time.Sleep(time.Second * 15)
	fmt.Fprintf(w, "Welcome to GoLinuxcloud server!")
}

We can use POSTMAN to test our server. We can see that we have to wait for some seconds for the server to return the response.

 

Method 1: Set timeout for the http.Client

The timeout can be specified using the client struct of the HTTP package. We can specify the Timeout value when building the HTTP client. An important thing to note about HTTP Client is that it is only created once and the same instance is used for making multiple HTTP requests. This method covers the entire exchange, from Dial (if a connection is not reused) to reading the body.

type Client struct {
	Transport RoundTripper
	CheckRedirect func(req *Request, via []*Request) error
	Jar CookieJar
	Timeout time.Duration
}

Now let’s take a look at the example below to understand how we can set the timeout (3 seconds) for http.Client in Golang:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

func main() {

	url := "http://localhost:8080/"
	method := "GET"

	client := &http.Client{
		// set the time out
		Timeout: 5 * time.Second,
	}
	req, err := http.NewRequest(method, url, nil)

	if err != nil {
		fmt.Println(err)
		return
	}
	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(body))
}

Output:

Get "http://localhost:8080/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

 

Method 2: Set up the timeout for the Transport

There are a number of other specific timeouts we can set for our Transport:

  • net.Dialer.Timeout limits the time spent establishing a TCP connection (if a new one is needed).
  • http.Transport.TLSHandshakeTimeout limits the time spent performing the TLS handshake.
  • http.Transport.ResponseHeaderTimeout limits the time spent reading the headers of the response.

In addition to the connect timeout, you can also set up the read/write timeout by using the code below:

package main

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"
)

func main() {

	url := "http://localhost:8080/"
	method := "GET"

	client := &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   3 * time.Second,
				KeepAlive: 3 * time.Second,
			}).Dial,
			TLSHandshakeTimeout:   3 * time.Second,
			ResponseHeaderTimeout: 3 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	}
	req, err := http.NewRequest(method, url, nil)

	if err != nil {
		fmt.Println(err)
		return
	}
	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(body))
}

Output:

Get "http://localhost:8080/": net/http: timeout awaiting response headers

 

Method 3: Set up the timeout for the Context

If you want to set time out for each request, you can do it by setting the Context as shown below:

package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

func main() {
	url := "http://localhost:8080/"

	// initial the context
	ctx, cncl := context.WithTimeout(context.Background(), time.Second*3)
	defer cncl()

	// initial the request with time out
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

	if err != nil {
		fmt.Println(err)
		return
	}

	// do the request
	res, err := http.DefaultClient.Do(req.WithContext(ctx))

	if err != nil {
		fmt.Println(err)
		return
	}

	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(body))
}

Output:

Get "http://localhost:8080/": context deadline exceeded

 

Summary

In this tutorial, I already show you three ways to set the timeout for HTTP requests. We can set up the timeout for http.Client in case you want to set up the entire exchange, from Dial (if a connection is not reused) to reading the body. For a more complex situation of sending a request, consider setting the Transport. We can specify the timeout for establishing a TCP or reading headers of the response. The final method is setting the timeout for the context. The difference between these methods:

  • Using context is for some requests while using the Client timeout might be applied to all requests.
  • Use context if you want to customize your deadline or timeout to each request; otherwise, use client timeout if you want a single timeout for every request.

 

References

https://en.wikipedia.org/wiki/Timeout_(computing)
https://datatracker.ietf.org/doc/id/draft-thomson-hybi-http-timeout-00.html
https://pkg.go.dev/net

 

Views: 209
Tuan Nguyen

Tuan Nguyen

He is proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment