From 2f3bcb69b1b2a8a693a72c5b48060fb9abd3fe9b Mon Sep 17 00:00:00 2001 From: bigflood Date: Wed, 13 May 2020 16:09:31 +0900 Subject: [PATCH] Read body to completion before close (#59) * The default HTTP client's Transport may not reuse HTTP/1.x "keep-alive" TCP connections if the Body is not read to completion and closed. * See https://golang.org/pkg/net/http/#Response --- sling.go | 7 +++++++ sling_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/sling.go b/sling.go index cc4ecc1..889407d 100644 --- a/sling.go +++ b/sling.go @@ -3,6 +3,7 @@ package sling import ( "encoding/base64" "io" + "io/ioutil" "net/http" "net/url" @@ -384,6 +385,12 @@ func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Res // when err is nil, resp contains a non-nil resp.Body which must be closed defer resp.Body.Close() + // The default HTTP client's Transport may not + // reuse HTTP/1.x "keep-alive" TCP connections if the Body is + // not read to completion and closed. + // See: https://golang.org/pkg/net/http/#Response + defer io.Copy(ioutil.Discard, resp.Body) + // Don't try to decode on 204s if resp.StatusCode == http.StatusNoContent { return resp, nil diff --git a/sling_test.go b/sling_test.go index b6f3b12..132edec 100644 --- a/sling_test.go +++ b/sling_test.go @@ -2,17 +2,20 @@ package sling import ( "bytes" + "context" "encoding/xml" "errors" "fmt" "io" "io/ioutil" "math" + "net" "net/http" "net/http/httptest" "net/url" "reflect" "strings" + "sync/atomic" "testing" ) @@ -890,6 +893,45 @@ func TestReceive_errorCreatingRequest(t *testing.T) { } } +func TestReuseTcpConnections(t *testing.T) { + var connCount int32 + + ln, _ := net.Listen("tcp", ":0") + rawURL := fmt.Sprintf("http://%s/", ln.Addr()) + + server := http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertMethod(t, "GET", r) + fmt.Fprintf(w, `{"text": "Some text"}`) + }), + ConnState: func(conn net.Conn, state http.ConnState) { + if state == http.StateNew { + atomic.AddInt32(&connCount, 1) + } + }, + } + + go server.Serve(ln) + + endpoint := New().Client(http.DefaultClient).Base(rawURL).Path("foo/").Get("get") + + for i := 0; i < 10; i++ { + resp, err := endpoint.New().Receive(nil, nil) + if err != nil { + t.Errorf("expected nil, got %v", err) + } + if resp.StatusCode != 200 { + t.Errorf("expected %d, got %d", 200, resp.StatusCode) + } + } + + server.Shutdown(context.Background()) + + if count := atomic.LoadInt32(&connCount); count != 1 { + t.Errorf("expected 1, got %v", count) + } +} + // Testing Utils // testServer returns an http Client, ServeMux, and Server. The client proxies