HTTP was a single request-and-response model. An
HTTP client opens the
TCP connection, requests a resource, gets the response, and the connection is closed. And establishing and terminating each
TCP connection is a resource-consuming operation (in detail, you can refer to my previous article). As the web application becomes more and more complex, displaying a single page may require several HTTP requests, too many TCP connection operations will have a bad impact on the performance.
persistent-connection (which is also called
keep-alive) model is created in
HTTP/1.1 protocol. In this model, TCP connections keep open between several successive requests, and in this way, the time needed to open new connections will be reduced.
In this article, I will show you how
persistent connection works based on a Golang application. We will do some experiments based on the demo app, and verify the TCP connection behavior with some popular network packet analysis tools. In short, After reading this article, you will learn:
http.Clientusage (and a little bit source code analysis)
- network analysis with
You can find the demo Golang application in this Github repo.
Let’s start from the simple case where the client keeps sending
sequential requests to the server. The code goes as follows:
We start an HTTP server in a Goroutine, and keep sending ten sequential requests to it. Right? Let’s run the application and check the numbers and status of TCP connections.
After running the above code, you can see the following output:
When the application stops running, we can run the following
netstat -n | grep 8080
The TCP connections are listed as follows:
Obviously, the 10 HTTP requests are not persistent since 10 TCP connections are opened.
Note: the last column of
netstat shows the state of TCP connection. The state of TCP connection termination process can be explained with the following image:
I will not cover the details in this article. But we need to understand the meaning of
four-way handshake process, the client will send the
ACK packet to terminate the connection, but the state of TCP can’t immediately go to
CLOSED. The client has to wait for some time and the state in this waiting process is called
TIME-WAIT. The TCP connection needs this
TIME-WAIT state for two main reasons.
- The first is to provide enough time that the
ACKis received by the other peer.
- The second is to provide a buffer period between the end of current connection and any subsequent ones. If not for this period, it’s possible that packets from different connections could be mixed. In detail, you can refer to this book.
In our demo application case, if you wait for a while after the program stops, and run the
netstat command again then no TCP connection will be listed in the output since they’re all closed.
Another tool to verify the TCP connections is
tcpdump, which can capture every network packet send to your machine. In our case, you can run the following
sudo tcpdump -i any -n host localhost
It will capture all the network packets send from or to the localhost (we’re running the server in localhost, right?).
tcpdump is a great tool to help you understand the network, you can refer to its document for more help.
Note: in our demo code above, we send 10 HTTP requests in sequence, which will make the capture result from
tcpdump too long. So I modified the for loop to only send 2 sequential requests, which is enough to verify the behavior of
persistent connection. The result goes as follows:
tcpdump output, the
Flag [S] represents
SYN flag, which is used to establish the TCP connection. The above snapshot contains two
Flag [S] packets. The first
Flag [S] is triggered by the first HTTP call, and the following packets are HTTP request and response. Then you can see the second
Flag [S] packet to open a new TCP connection, which means the second HTTP request is not
persistent connection as we hope.
Next step, let’s see how to make HTTP work as a persistent connection in Golang.
In fact,this is a well known issue in Golang ecosystem, you can find the information in the official document:
- If the returned error is nil, the Response will contain a non-nil Body which the user is expected to close. If the Body is not both read to EOF and closed, the Client’s underlying RoundTripper (typically Transport) may not be able to re-use a persistent TCP connection to the server for a subsequent “keep-alive” request.
The fix will be straightforward by just adding two more lines of code as follows:
let’s verify by running
netstat command, the result goes as follows:
This time 10 sequential HTTP requests establish only one TCP connection. This behavior is just what we hope:
We can double verify it by doing the same experiment as above: run two HTTP requests in sequence and capture packets with
This time, only one
Flag [S] packet is there! The two sequential HTTP request re-use the same underlying TCP connection.
In this article, we showed how HTTP
persistent connection works in the case of sequential requests. In the next article, we can show you the case of concurrent requests.