Created
December 17, 2018 09:41
-
-
Save athomason/78e8a626b84c968d5806674d973b0553 to your computer and use it in GitHub Desktop.
net/http: draining response bodies to enable connection reuse
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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