Commit 9aadbf57 authored by Meng Zhuo's avatar Meng Zhuo Committed by Brad Fitzpatrick

net/http: prevent transport sends two "Connection: close" headers

There are three functions that do Connection header write:
1. transport.go/ persistConn.roundTrip
2. transfer.go/ transferWriter.writeHeader
3. request.go/ Request.write

The root cause is roundTrip didn't lookup into request.Close and
transferWriter
didn't take care of extraHeaders.

Fixes #28886

Change-Id: I1d131019c7cd42eb1bcc972c631b7df7511c1f39
Reviewed-on: https://go-review.googlesource.com/c/150722
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 4f15b547
......@@ -1330,6 +1330,9 @@ func (r *Request) wantsHttp10KeepAlive() bool {
}
func (r *Request) wantsClose() bool {
if r.Close {
return true
}
return hasToken(r.Header.get("Connection"), "close")
}
......
......@@ -2135,7 +2135,7 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err
continueCh = make(chan struct{}, 1)
}
if pc.t.DisableKeepAlives {
if pc.t.DisableKeepAlives && !req.wantsClose() {
req.extraHeaders().Set("Connection", "close")
}
......
......@@ -41,6 +41,8 @@ import (
"sync/atomic"
"testing"
"time"
"golang_org/x/net/http/httpguts"
)
// TODO: test 5 pipelined requests with responses: 1) OK, 2) OK, Connection: Close
......@@ -310,6 +312,58 @@ func TestTransportConnectionCloseOnRequestDisableKeepAlive(t *testing.T) {
}
}
// Test that Transport only sends one "Connection: close", regardless of
// how "close" was indicated.
func TestTransportRespectRequestWantsClose(t *testing.T) {
tests := []struct {
disableKeepAlives bool
close bool
}{
{disableKeepAlives: false, close: false},
{disableKeepAlives: false, close: true},
{disableKeepAlives: true, close: false},
{disableKeepAlives: true, close: true},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("DisableKeepAlive=%v,RequestClose=%v", tc.disableKeepAlives, tc.close),
func(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(hostPortHandler)
defer ts.Close()
c := ts.Client()
c.Transport.(*Transport).DisableKeepAlives = tc.disableKeepAlives
req, err := NewRequest("GET", ts.URL, nil)
if err != nil {
t.Fatal(err)
}
count := 0
trace := &httptrace.ClientTrace{
WroteHeaderField: func(key string, field []string) {
if key != "Connection" {
return
}
if httpguts.HeaderValuesContainsToken(field, "close") {
count += 1
}
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
req.Close = tc.close
res, err := c.Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if want := tc.disableKeepAlives || tc.close; count > 1 || (count == 1) != want {
t.Errorf("expecting want:%v, got 'Connection: close':%d", want, count)
}
})
}
}
func TestTransportIdleCacheKeys(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(hostPortHandler)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment