Husni Munaya

How to Reuse HTTP Connection in Go

April 07, 2021

Go’s standard HTTP client supports HTTP keep-alive, which keeps the TCP connection open to be reused for subsequent HTTP request. You have to read the body request to completion before you close it as stated in Response.Body documentation. If you close the request body when it’s not fully read, the TCP connection can’t be reused because there’s still data left on the network to be read. Thus, the TCP connection will be closed.

Let’s take a look at the following program.

package main

import (
	"net/http"
)

func main() {
	for i := 0; i < 10; i++ {
		res, err := http.Get("http://127.0.0.1:8080")
		if err != nil {
			panic(err)
		}

		res.Body.Close()
	}
}

It’s a simple program that tries to connect to a local web server on my local machine. It closes the response body without reading it. We can monitor the network activity with netstat to see how many TCP connections are open while running the program.

netstat -atn | grep 8080
tcp        0      0 127.0.0.1:44390         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44374         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44380         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44392         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44388         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44384         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44378         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44386         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44382         127.0.0.1:8080          TIME_WAIT
tcp        0      0 127.0.0.1:44376         127.0.0.1:8080          TIME_WAIT
tcp6       0      0 :::8080                 :::*                    LISTEN

As expected, there are multiple open TCP connections.

Now, modify the program as follows: the program now reads all of the request body before closing it.

package main

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

func main() {
	for i := 0; i < 10; i++ {
		res, err := http.Get("http://127.0.0.1:8080")
		if err != nil {
			panic(err)
		}

		io.Copy(ioutil.Discard, res.Body)
		res.Body.Close()
	}
}

Run netstat to see how many TCP connections are open.

netstat -atn | grep 8080
tcp        0      0 127.0.0.1:44332         127.0.0.1:8080          TIME_WAIT
tcp6       0      0 :::8080                 :::*                    LISTEN

You’ll see that there’s only one TCP connections while running the program. That means the program successfully reuses the same TCP connection.

You should fully read the response body before you close it to ensure that the TCP connection is reused. By reusing the same TCP connection, we can avoid the overhead in establishing a new TCP connection, thus providing a significant performance to a program that makes a lot of HTTP requests. If you don’t really need the request body, you can limit the reading with io.CopyN in case the the other end of a connection sends you unwanted large streams of data.