Skip to content

Commit ea2376f

Browse files
committed
net/http: make Transport.RoundTrip return raw Conn.Read error on peek failure
From at least Go 1.4 to Go 1.6, Transport.RoundTrip would return the error value from net.Conn.Read directly when the initial Read (1 byte Peek) failed while reading the HTTP response, if a request was outstanding. While never a documented or tested promise, Go 1.7 changed the behavior (starting at https://golang.org/cl/23160). This restores the old behavior and adds a test (but no documentation promises yet) while keeping the fix for spammy logging reported in #15446. This looks larger than it is: it just changes errServerClosedConn from a variable to a type, where the type preserves the underlying net.Conn.Read error, for unwrapping later in Transport.RoundTrip. Fixes #16465 Change-Id: I6fa018991221e93c0cfe3e4129cb168fbd98bd27 Reviewed-on: https://go-review.googlesource.com/25153 Reviewed-by: Andrew Gerrand <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 67f799c commit ea2376f

File tree

3 files changed

+87
-8
lines changed

3 files changed

+87
-8
lines changed

src/net/http/transport.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,11 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
383383
return resp, nil
384384
}
385385
if !pconn.shouldRetryRequest(req, err) {
386+
// Issue 16465: return underlying net.Conn.Read error from peek,
387+
// as we've historically done.
388+
if e, ok := err.(transportReadFromServerError); ok {
389+
err = e.err
390+
}
386391
return nil, err
387392
}
388393
testHookRoundTripRetried()
@@ -415,11 +420,19 @@ func (pc *persistConn) shouldRetryRequest(req *Request, err error) bool {
415420
// first, per golang.org/issue/15723
416421
return false
417422
}
418-
if _, ok := err.(nothingWrittenError); ok {
423+
switch err.(type) {
424+
case nothingWrittenError:
419425
// We never wrote anything, so it's safe to retry.
420426
return true
427+
case transportReadFromServerError:
428+
// We got some non-EOF net.Conn.Read failure reading
429+
// the 1st response byte from the server.
430+
return true
421431
}
422-
if err == errServerClosedIdle || err == errServerClosedConn {
432+
if err == errServerClosedIdle {
433+
// The server replied with io.EOF while we were trying to
434+
// read the response. Probably an unfortunately keep-alive
435+
// timeout, just as the client was writing a request.
423436
return true
424437
}
425438
return false // conservatively
@@ -566,10 +579,25 @@ var (
566579
errCloseIdleConns = errors.New("http: CloseIdleConnections called")
567580
errReadLoopExiting = errors.New("http: persistConn.readLoop exiting")
568581
errServerClosedIdle = errors.New("http: server closed idle connection")
569-
errServerClosedConn = errors.New("http: server closed connection")
570582
errIdleConnTimeout = errors.New("http: idle connection timeout")
571583
)
572584

585+
// transportReadFromServerError is used by Transport.readLoop when the
586+
// 1 byte peek read fails and we're actually anticipating a response.
587+
// Usually this is just due to the inherent keep-alive shut down race,
588+
// where the server closed the connection at the same time the client
589+
// wrote. The underlying err field is usually io.EOF or some
590+
// ECONNRESET sort of thing which varies by platform. But it might be
591+
// the user's custom net.Conn.Read error too, so we carry it along for
592+
// them to return from Transport.RoundTrip.
593+
type transportReadFromServerError struct {
594+
err error
595+
}
596+
597+
func (e transportReadFromServerError) Error() string {
598+
return fmt.Sprintf("net/http: Transport failed to read from server: %v", e.err)
599+
}
600+
573601
func (t *Transport) putOrCloseIdleConn(pconn *persistConn) {
574602
if err := t.tryPutIdleConn(pconn); err != nil {
575603
pconn.close(err)
@@ -1293,7 +1321,10 @@ func (pc *persistConn) mapRoundTripErrorFromReadLoop(startBytesWritten int64, er
12931321
if pc.isCanceled() {
12941322
return errRequestCanceled
12951323
}
1296-
if err == errServerClosedIdle || err == errServerClosedConn {
1324+
if err == errServerClosedIdle {
1325+
return err
1326+
}
1327+
if _, ok := err.(transportReadFromServerError); ok {
12971328
return err
12981329
}
12991330
if pc.isBroken() {
@@ -1314,7 +1345,11 @@ func (pc *persistConn) mapRoundTripErrorAfterClosed(startBytesWritten int64) err
13141345
return errRequestCanceled
13151346
}
13161347
err := pc.closed
1317-
if err == errServerClosedIdle || err == errServerClosedConn {
1348+
if err == errServerClosedIdle {
1349+
// Don't decorate
1350+
return err
1351+
}
1352+
if _, ok := err.(transportReadFromServerError); ok {
13181353
// Don't decorate
13191354
return err
13201355
}
@@ -1383,7 +1418,7 @@ func (pc *persistConn) readLoop() {
13831418
if err == nil {
13841419
resp, err = pc.readResponse(rc, trace)
13851420
} else {
1386-
err = errServerClosedConn
1421+
err = transportReadFromServerError{err}
13871422
closeErr = err
13881423
}
13891424

src/net/http/transport_internal_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,22 @@ func TestTransportPersistConnReadLoopEOF(t *testing.T) {
4646
conn.Close() // simulate the server hanging up on the client
4747

4848
_, err = pc.roundTrip(treq)
49-
if err != errServerClosedConn && err != errServerClosedIdle {
49+
if !isTransportReadFromServerError(err) && err != errServerClosedIdle {
5050
t.Fatalf("roundTrip = %#v, %v; want errServerClosedConn or errServerClosedIdle", err, err)
5151
}
5252

5353
<-pc.closech
5454
err = pc.closed
55-
if err != errServerClosedConn && err != errServerClosedIdle {
55+
if !isTransportReadFromServerError(err) && err != errServerClosedIdle {
5656
t.Fatalf("pc.closed = %#v, %v; want errServerClosedConn or errServerClosedIdle", err, err)
5757
}
5858
}
5959

60+
func isTransportReadFromServerError(err error) bool {
61+
_, ok := err.(transportReadFromServerError)
62+
return ok
63+
}
64+
6065
func newLocalListener(t *testing.T) net.Listener {
6166
ln, err := net.Listen("tcp", "127.0.0.1:0")
6267
if err != nil {

src/net/http/transport_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3511,6 +3511,45 @@ func TestTransportIdleConnTimeout(t *testing.T) {
35113511
}
35123512
}
35133513

3514+
type funcConn struct {
3515+
net.Conn
3516+
read func([]byte) (int, error)
3517+
write func([]byte) (int, error)
3518+
}
3519+
3520+
func (c funcConn) Read(p []byte) (int, error) { return c.read(p) }
3521+
func (c funcConn) Write(p []byte) (int, error) { return c.write(p) }
3522+
func (c funcConn) Close() error { return nil }
3523+
3524+
// Issue 16465: Transport.RoundTrip should return the raw net.Conn.Read error from Peek
3525+
// back to the caller.
3526+
func TestTransportReturnsPeekError(t *testing.T) {
3527+
errValue := errors.New("specific error value")
3528+
3529+
wrote := make(chan struct{})
3530+
var wroteOnce sync.Once
3531+
3532+
tr := &Transport{
3533+
Dial: func(network, addr string) (net.Conn, error) {
3534+
c := funcConn{
3535+
read: func([]byte) (int, error) {
3536+
<-wrote
3537+
return 0, errValue
3538+
},
3539+
write: func(p []byte) (int, error) {
3540+
wroteOnce.Do(func() { close(wrote) })
3541+
return len(p), nil
3542+
},
3543+
}
3544+
return c, nil
3545+
},
3546+
}
3547+
_, err := tr.RoundTrip(httptest.NewRequest("GET", "http://fake.tld/", nil))
3548+
if err != errValue {
3549+
t.Errorf("error = %#v; want %v", err, errValue)
3550+
}
3551+
}
3552+
35143553
var errFakeRoundTrip = errors.New("fake roundtrip")
35153554

35163555
type funcRoundTripper func()

0 commit comments

Comments
 (0)