Skip to content

Commit cc2c5fc

Browse files
committed
net/http: don't re-use Transport connections if we've seen an EOF
This the second part of making persistent HTTPS connections to certain servers (notably Amazon) robust. See the story in part 1: https://golang.org/cl/76400046/ This is the http Transport change that notes whether our net.Conn.Read has ever seen an EOF. If it has, then we use that as an additional signal to not re-use that connection (in addition to the HTTP response headers) Fixes #3514 LGTM=rsc R=agl, rsc CC=golang-codereviews https://golang.org/cl/79240044
1 parent f61f18d commit cc2c5fc

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/pkg/net/http/transport.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) {
588588
pconn.conn = tlsConn
589589
}
590590

591-
pconn.br = bufio.NewReader(pconn.conn)
591+
pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF})
592592
pconn.bw = bufio.NewWriter(pconn.conn)
593593
go pconn.readLoop()
594594
go pconn.writeLoop()
@@ -723,6 +723,7 @@ type persistConn struct {
723723
tlsState *tls.ConnectionState
724724
closed bool // whether conn has been closed
725725
br *bufio.Reader // from conn
726+
sawEOF bool // whether we've seen EOF from conn; owned by readLoop
726727
bw *bufio.Writer // to conn
727728
reqch chan requestAndChan // written by roundTrip; read by readLoop
728729
writech chan writeRequest // written by roundTrip; read by writeLoop
@@ -841,6 +842,9 @@ func (pc *persistConn) readLoop() {
841842
if err != nil {
842843
alive1 = false
843844
}
845+
if alive1 && pc.sawEOF {
846+
alive1 = false
847+
}
844848
if alive1 && !pc.t.putIdleConn(pc) {
845849
alive1 = false
846850
}
@@ -1134,3 +1138,16 @@ type tlsHandshakeTimeoutError struct{}
11341138
func (tlsHandshakeTimeoutError) Timeout() bool { return true }
11351139
func (tlsHandshakeTimeoutError) Temporary() bool { return true }
11361140
func (tlsHandshakeTimeoutError) Error() string { return "net/http: TLS handshake timeout" }
1141+
1142+
type noteEOFReader struct {
1143+
r io.Reader
1144+
sawEOF *bool
1145+
}
1146+
1147+
func (nr noteEOFReader) Read(p []byte) (n int, err error) {
1148+
n, err = nr.r.Read(p)
1149+
if err == io.EOF {
1150+
*nr.sawEOF = true
1151+
}
1152+
return
1153+
}

src/pkg/net/http/transport_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"bytes"
1212
"compress/gzip"
1313
"crypto/rand"
14+
"crypto/tls"
1415
"errors"
1516
"fmt"
1617
"io"
@@ -1836,6 +1837,71 @@ func TestTransportTLSHandshakeTimeout(t *testing.T) {
18361837
}
18371838
}
18381839

1840+
// Trying to repro golang.org/issue/3514
1841+
func TestTLSServerClosesConnection(t *testing.T) {
1842+
defer afterTest(t)
1843+
closedc := make(chan bool, 1)
1844+
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
1845+
if strings.Contains(r.URL.Path, "/keep-alive-then-die") {
1846+
conn, _, _ := w.(Hijacker).Hijack()
1847+
conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo"))
1848+
conn.Close()
1849+
closedc <- true
1850+
return
1851+
}
1852+
fmt.Fprintf(w, "hello")
1853+
}))
1854+
defer ts.Close()
1855+
tr := &Transport{
1856+
TLSClientConfig: &tls.Config{
1857+
InsecureSkipVerify: true,
1858+
},
1859+
}
1860+
defer tr.CloseIdleConnections()
1861+
client := &Client{Transport: tr}
1862+
1863+
var nSuccess = 0
1864+
var errs []error
1865+
const trials = 20
1866+
for i := 0; i < trials; i++ {
1867+
tr.CloseIdleConnections()
1868+
res, err := client.Get(ts.URL + "/keep-alive-then-die")
1869+
if err != nil {
1870+
t.Fatal(err)
1871+
}
1872+
<-closedc
1873+
slurp, err := ioutil.ReadAll(res.Body)
1874+
if err != nil {
1875+
t.Fatal(err)
1876+
}
1877+
if string(slurp) != "foo" {
1878+
t.Errorf("Got %q, want foo", slurp)
1879+
}
1880+
1881+
// Now try again and see if we successfully
1882+
// pick a new connection.
1883+
res, err = client.Get(ts.URL + "/")
1884+
if err != nil {
1885+
errs = append(errs, err)
1886+
continue
1887+
}
1888+
slurp, err = ioutil.ReadAll(res.Body)
1889+
if err != nil {
1890+
errs = append(errs, err)
1891+
continue
1892+
}
1893+
nSuccess++
1894+
}
1895+
if nSuccess > 0 {
1896+
t.Logf("successes = %d of %d", nSuccess, trials)
1897+
} else {
1898+
t.Errorf("All runs failed:")
1899+
}
1900+
for _, err := range errs {
1901+
t.Logf(" err: %v", err)
1902+
}
1903+
}
1904+
18391905
func newLocalListener(t *testing.T) net.Listener {
18401906
ln, err := net.Listen("tcp", "127.0.0.1:0")
18411907
if err != nil {

0 commit comments

Comments
 (0)