Skip to content

Instantly share code, notes, and snippets.

@athomason
Created December 17, 2018 09:41
Show Gist options
  • Save athomason/78e8a626b84c968d5806674d973b0553 to your computer and use it in GitHub Desktop.
Save athomason/78e8a626b84c968d5806674d973b0553 to your computer and use it in GitHub Desktop.
net/http: draining response bodies to enable connection reuse
/*
Tags: golang http response drain copy close
Q. Does it matter if you drain an http.Transport's Response.Body before
Close'ing it?
A. Yes, failing to read a non-empty Body until EOF can make a connection not
reusable.
Demo: this program uses the net/http DefaultClient to request a single path in
a serial loop.
Run it with and without -drain to observe:
1. Without -drain (default), the client does not consume the http.Response
Body, and opens a new connection for every request.
2. With -drain, the client opens only one connection.
Note that any non-zero -body-size triggers this behavior, as of go1.11.4.
*/
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
)
func main() {
drain := flag.Bool("drain", false, "drain response body; defaults to leaving responses unconsumed")
bodySize := flag.Int("body-size", 16<<10, "response body size")
flag.Parse()
l, err := net.Listen("tcp", ":0")
check(err)
// http server that prints when a new client opens a connection
go func() {
payload := make([]byte, *bodySize)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(payload)
})
s := http.Server{
ConnState: func(c net.Conn, state http.ConnState) {
if state == http.StateNew {
fmt.Println(c.LocalAddr())
}
},
}
check(s.Serve(l))
}()
// http client that drains its http.Response or not
url := "http://" + l.Addr().String()
for {
resp, err := http.Get(url)
check(err)
if *drain {
io.Copy(ioutil.Discard, resp.Body)
}
check(resp.Body.Close())
}
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment