Skip to content

Commit

Permalink
http2: close conns after use when req.Close is set
Browse files Browse the repository at this point in the history
Avoid reusing connections after receiving a response to a request
for which cc.Close is set or a "Connection: close" header is present.

Adjust the tests for connection reuse to test both the case where
new conns are created by the http2 package and when they are
created by the net/http package.

Fixes golang/go#49375

Change-Id: I58594972c832a20b66a3910c17acb51a98a9f7a5
Reviewed-on: https://go-review.googlesource.com/c/net/+/361498
Trust: Damien Neil <dneil@google.com>
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
  • Loading branch information
neild committed Nov 12, 2021
1 parent f0573a1 commit 69e39ba
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 17 deletions.
3 changes: 3 additions & 0 deletions http2/transport.go
Expand Up @@ -1213,6 +1213,9 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
return err
}
cc.addStreamLocked(cs) // assigns stream ID
if isConnectionCloseRequest(req) {
cc.doNotReuse = true
}
cc.mu.Unlock()

// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
Expand Down
59 changes: 42 additions & 17 deletions http2/transport_test.go
Expand Up @@ -190,22 +190,32 @@ func TestTransport(t *testing.T) {
}
}

func onSameConn(t *testing.T, modReq func(*http.Request)) bool {
func testTransportReusesConns(t *testing.T, useClient, wantSame bool, modReq func(*http.Request)) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.RemoteAddr)
}, optOnlyServer, func(c net.Conn, st http.ConnState) {
t.Logf("conn %v is now state %v", c.RemoteAddr(), st)
})
defer st.Close()
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
if useClient {
tr.ConnPool = noDialClientConnPool{new(clientConnPool)}
}
defer tr.CloseIdleConnections()
get := func() string {
req, err := http.NewRequest("GET", st.ts.URL, nil)
if err != nil {
t.Fatal(err)
}
modReq(req)
res, err := tr.RoundTrip(req)
var res *http.Response
if useClient {
c := st.ts.Client()
ConfigureTransports(c.Transport.(*http.Transport))
res, err = c.Do(req)
} else {
res, err = tr.RoundTrip(req)
}
if err != nil {
t.Fatal(err)
}
Expand All @@ -222,24 +232,39 @@ func onSameConn(t *testing.T, modReq func(*http.Request)) bool {
}
first := get()
second := get()
return first == second
}

func TestTransportReusesConns(t *testing.T) {
if !onSameConn(t, func(*http.Request) {}) {
t.Errorf("first and second responses were on different connections")
if got := first == second; got != wantSame {
t.Errorf("first and second responses on same connection: %v; want %v", got, wantSame)
}
}

func TestTransportReusesConn_RequestClose(t *testing.T) {
if onSameConn(t, func(r *http.Request) { r.Close = true }) {
t.Errorf("first and second responses were not on different connections")
}
}

func TestTransportReusesConn_ConnClose(t *testing.T) {
if onSameConn(t, func(r *http.Request) { r.Header.Set("Connection", "close") }) {
t.Errorf("first and second responses were not on different connections")
func TestTransportReusesConns(t *testing.T) {
for _, test := range []struct {
name string
modReq func(*http.Request)
wantSame bool
}{{
name: "ReuseConn",
modReq: func(*http.Request) {},
wantSame: true,
}, {
name: "RequestClose",
modReq: func(r *http.Request) { r.Close = true },
wantSame: false,
}, {
name: "ConnClose",
modReq: func(r *http.Request) { r.Header.Set("Connection", "close") },
wantSame: false,
}} {
t.Run(test.name, func(t *testing.T) {
t.Run("Transport", func(t *testing.T) {
const useClient = false
testTransportReusesConns(t, useClient, test.wantSame, test.modReq)
})
t.Run("Client", func(t *testing.T) {
const useClient = true
testTransportReusesConns(t, useClient, test.wantSame, test.modReq)
})
})
}
}

Expand Down

0 comments on commit 69e39ba

Please sign in to comment.