Skip to content

Commit f5de73e

Browse files
committed
http2: make Transport respect http1 Transport settings
The http2 Transport now respects the http1 Transport's DisableCompression, DisableKeepAlives, and ResponseHeaderTimeout, if the http2 and http1 Transports are wired up together, as they are in the upcoming Go 1.6. Updates golang/go#14008 Change-Id: I2f477f6fe5dbef9d0e5439dfc7f3ec2c0da7f296 Reviewed-on: https://go-review.googlesource.com/18721 Reviewed-by: Andrew Gerrand <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 5c0dae8 commit f5de73e

File tree

4 files changed

+350
-40
lines changed

4 files changed

+350
-40
lines changed

http2/configure_transport.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import (
1414

1515
func configureTransport(t1 *http.Transport) (*Transport, error) {
1616
connPool := new(clientConnPool)
17-
t2 := &Transport{ConnPool: noDialClientConnPool{connPool}}
17+
t2 := &Transport{
18+
ConnPool: noDialClientConnPool{connPool},
19+
t1: t1,
20+
}
21+
connPool.t = t2
1822
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
1923
return nil, err
2024
}

http2/http2.go

+11
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,14 @@ func bodyAllowedForStatus(status int) bool {
285285
}
286286
return true
287287
}
288+
289+
type httpError struct {
290+
msg string
291+
timeout bool
292+
}
293+
294+
func (e *httpError) Error() string { return e.msg }
295+
func (e *httpError) Timeout() bool { return e.timeout }
296+
func (e *httpError) Temporary() bool { return true }
297+
298+
var errTimeout error = &httpError{msg: "http2: timeout awaiting response headers", timeout: true}

http2/transport.go

+112-38
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"strconv"
2323
"strings"
2424
"sync"
25+
"time"
2526

2627
"golang.org/x/net/http2/hpack"
2728
)
@@ -84,6 +85,11 @@ type Transport struct {
8485
// to mean no limit.
8586
MaxHeaderListSize uint32
8687

88+
// t1, if non-nil, is the standard library Transport using
89+
// this transport. Its settings are used (but not its
90+
// RoundTrip method, etc).
91+
t1 *http.Transport
92+
8793
connPoolOnce sync.Once
8894
connPoolOrDef ClientConnPool // non-nil version of ConnPool
8995
}
@@ -99,12 +105,7 @@ func (t *Transport) maxHeaderListSize() uint32 {
99105
}
100106

101107
func (t *Transport) disableCompression() bool {
102-
if t.DisableCompression {
103-
return true
104-
}
105-
// TODO: also disable if this transport is somehow linked to an http1 Transport
106-
// and it's configured there?
107-
return false
108+
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
108109
}
109110

110111
var errTransportVersion = errors.New("http2: ConfigureTransport is only supported starting at Go 1.6")
@@ -160,7 +161,7 @@ type ClientConn struct {
160161
henc *hpack.Encoder
161162
freeBuf [][]byte
162163

163-
wmu sync.Mutex // held while writing; acquire AFTER wmu if holding both
164+
wmu sync.Mutex // held while writing; acquire AFTER mu if holding both
164165
werr error // first write error that has occurred
165166
}
166167

@@ -178,7 +179,7 @@ type clientStream struct {
178179
inflow flow // guarded by cc.mu
179180
bytesRemain int64 // -1 means unknown; owned by transportResponseBody.Read
180181
readErr error // sticky read error; owned by transportResponseBody.Read
181-
stopReqBody bool // stop writing req body; guarded by cc.mu
182+
stopReqBody error // if non-nil, stop writing req body; guarded by cc.mu
182183

183184
peerReset chan struct{} // closed on peer reset
184185
resetErr error // populated before peerReset is closed
@@ -221,10 +222,13 @@ func (cs *clientStream) checkReset() error {
221222
}
222223
}
223224

224-
func (cs *clientStream) abortRequestBodyWrite() {
225+
func (cs *clientStream) abortRequestBodyWrite(err error) {
226+
if err == nil {
227+
panic("nil error")
228+
}
225229
cc := cs.cc
226230
cc.mu.Lock()
227-
cs.stopReqBody = true
231+
cs.stopReqBody = err
228232
cc.cond.Broadcast()
229233
cc.mu.Unlock()
230234
}
@@ -364,6 +368,12 @@ func (t *Transport) dialTLSDefault(network, addr string, cfg *tls.Config) (net.C
364368
return cn, nil
365369
}
366370

371+
// disableKeepAlives reports whether connections should be closed as
372+
// soon as possible.
373+
func (t *Transport) disableKeepAlives() bool {
374+
return t.t1 != nil && t.t1.DisableKeepAlives
375+
}
376+
367377
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
368378
if VerboseLogs {
369379
t.vlogf("http2: Transport creating client conn to %v", c.RemoteAddr())
@@ -463,7 +473,7 @@ func (cc *ClientConn) CanTakeNewRequest() bool {
463473
}
464474

465475
func (cc *ClientConn) canTakeNewRequestLocked() bool {
466-
return cc.goAway == nil &&
476+
return cc.goAway == nil && !cc.closed &&
467477
int64(len(cc.streams)+1) < int64(cc.maxConcurrentStreams) &&
468478
cc.nextStreamID < 2147483647
469479
}
@@ -544,6 +554,17 @@ func commaSeparatedTrailers(req *http.Request) (string, error) {
544554
return "", nil
545555
}
546556

557+
func (cc *ClientConn) responseHeaderTimeout() time.Duration {
558+
if cc.t.t1 != nil {
559+
return cc.t.t1.ResponseHeaderTimeout
560+
}
561+
// No way to do this (yet?) with just an http2.Transport. Probably
562+
// no need. Request.Cancel this is the new way. We only need to support
563+
// this for compatibility with the old http.Transport fields when
564+
// we're doing transparent http2.
565+
return 0
566+
}
567+
547568
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
548569
trailers, err := commaSeparatedTrailers(req)
549570
if err != nil {
@@ -623,17 +644,25 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
623644
return nil, werr
624645
}
625646

647+
var respHeaderTimer <-chan time.Time
626648
var bodyCopyErrc chan error // result of body copy
627649
if hasBody {
628650
bodyCopyErrc = make(chan error, 1)
629651
go func() {
630652
bodyCopyErrc <- cs.writeRequestBody(body, req.Body)
631653
}()
654+
} else {
655+
if d := cc.responseHeaderTimeout(); d != 0 {
656+
timer := time.NewTimer(d)
657+
defer timer.Stop()
658+
respHeaderTimer = timer.C
659+
}
632660
}
633661

634662
readLoopResCh := cs.resc
635663
requestCanceledCh := requestCancel(req)
636-
requestCanceled := false
664+
bodyWritten := false
665+
637666
for {
638667
select {
639668
case re := <-readLoopResCh:
@@ -648,7 +677,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
648677
// doesn't, they'll RST_STREAM us soon enough. This is a
649678
// heuristic to avoid adding knobs to Transport. Hopefully
650679
// we can keep it.
651-
cs.abortRequestBodyWrite()
680+
cs.abortRequestBodyWrite(errStopReqBodyWrite)
652681
}
653682
if re.err != nil {
654683
cc.forgetStreamID(cs.ID)
@@ -657,37 +686,37 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
657686
res.Request = req
658687
res.TLS = cc.tlsState
659688
return res, nil
689+
case <-respHeaderTimer:
690+
cc.forgetStreamID(cs.ID)
691+
if !hasBody || bodyWritten {
692+
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
693+
} else {
694+
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
695+
}
696+
return nil, errTimeout
660697
case <-requestCanceledCh:
661698
cc.forgetStreamID(cs.ID)
662-
cs.abortRequestBodyWrite()
663-
if !hasBody {
699+
if !hasBody || bodyWritten {
664700
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
665-
return nil, errRequestCanceled
701+
} else {
702+
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
666703
}
667-
// If we have a body, wait for the body write to be
668-
// finished before sending the RST_STREAM frame.
669-
requestCanceled = true
670-
requestCanceledCh = nil // to prevent spins
671-
readLoopResCh = nil // ignore responses at this point
704+
return nil, errRequestCanceled
672705
case <-cs.peerReset:
673-
if requestCanceled {
674-
// They hung up on us first. No need to write a RST_STREAM.
675-
// But prioritize the request canceled error value, since
676-
// it's likely related. (same spirit as http1 code)
677-
return nil, errRequestCanceled
678-
}
679706
// processResetStream already removed the
680707
// stream from the streams map; no need for
681708
// forgetStreamID.
682709
return nil, cs.resetErr
683710
case err := <-bodyCopyErrc:
684-
if requestCanceled {
685-
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
686-
return nil, errRequestCanceled
687-
}
688711
if err != nil {
689712
return nil, err
690713
}
714+
bodyWritten = true
715+
if d := cc.responseHeaderTimeout(); d != 0 {
716+
timer := time.NewTimer(d)
717+
defer timer.Stop()
718+
respHeaderTimer = timer.C
719+
}
691720
}
692721
}
693722
}
@@ -723,9 +752,14 @@ func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, hdrs []byte)
723752
return cc.werr
724753
}
725754

726-
// errAbortReqBodyWrite is an internal error value.
727-
// It doesn't escape to callers.
728-
var errAbortReqBodyWrite = errors.New("http2: aborting request body write")
755+
// internal error values; they don't escape to callers
756+
var (
757+
// abort request body write; don't send cancel
758+
errStopReqBodyWrite = errors.New("http2: aborting request body write")
759+
760+
// abort request body write, but send stream reset of cancel.
761+
errStopReqBodyWriteAndCancel = errors.New("http2: canceling request")
762+
)
729763

730764
func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (err error) {
731765
cc := cs.cc
@@ -761,7 +795,13 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
761795
for len(remain) > 0 && err == nil {
762796
var allowed int32
763797
allowed, err = cs.awaitFlowControl(len(remain))
764-
if err != nil {
798+
switch {
799+
case err == errStopReqBodyWrite:
800+
return err
801+
case err == errStopReqBodyWriteAndCancel:
802+
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
803+
return err
804+
case err != nil:
765805
return err
766806
}
767807
cc.wmu.Lock()
@@ -821,8 +861,8 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
821861
if cc.closed {
822862
return 0, errClientConnClosed
823863
}
824-
if cs.stopReqBody {
825-
return 0, errAbortReqBodyWrite
864+
if cs.stopReqBody != nil {
865+
return 0, cs.stopReqBody
826866
}
827867
if err := cs.checkReset(); err != nil {
828868
return 0, err
@@ -898,7 +938,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
898938
cc.writeHeader(lowKey, v)
899939
}
900940
}
901-
if contentLength >= 0 {
941+
if shouldSendReqContentLength(req.Method, contentLength) {
902942
cc.writeHeader("content-length", strconv.FormatInt(contentLength, 10))
903943
}
904944
if addGzipHeader {
@@ -910,6 +950,28 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
910950
return cc.hbuf.Bytes()
911951
}
912952

953+
// shouldSendReqContentLength reports whether the http2.Transport should send
954+
// a "content-length" request header. This logic is basically a copy of the net/http
955+
// transferWriter.shouldSendContentLength.
956+
// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
957+
// -1 means unknown.
958+
func shouldSendReqContentLength(method string, contentLength int64) bool {
959+
if contentLength > 0 {
960+
return true
961+
}
962+
if contentLength < 0 {
963+
return false
964+
}
965+
// For zero bodies, whether we send a content-length depends on the method.
966+
// It also kinda doesn't matter for http2 either way, with END_STREAM.
967+
switch method {
968+
case "POST", "PUT", "PATCH":
969+
return true
970+
default:
971+
return false
972+
}
973+
}
974+
913975
// requires cc.mu be held.
914976
func (cc *ClientConn) encodeTrailers(req *http.Request) []byte {
915977
cc.hbuf.Reset()
@@ -1032,6 +1094,8 @@ func (rl *clientConnReadLoop) cleanup() {
10321094

10331095
func (rl *clientConnReadLoop) run() error {
10341096
cc := rl.cc
1097+
closeWhenIdle := cc.t.disableKeepAlives()
1098+
gotReply := false // ever saw a reply
10351099
for {
10361100
f, err := cc.fr.ReadFrame()
10371101
if err != nil {
@@ -1046,18 +1110,25 @@ func (rl *clientConnReadLoop) run() error {
10461110
if VerboseLogs {
10471111
cc.vlogf("http2: Transport received %s", summarizeFrame(f))
10481112
}
1113+
maybeClose := false // whether frame might transition us to idle
10491114

10501115
switch f := f.(type) {
10511116
case *HeadersFrame:
10521117
err = rl.processHeaders(f)
1118+
maybeClose = true
1119+
gotReply = true
10531120
case *ContinuationFrame:
10541121
err = rl.processContinuation(f)
1122+
maybeClose = true
10551123
case *DataFrame:
10561124
err = rl.processData(f)
1125+
maybeClose = true
10571126
case *GoAwayFrame:
10581127
err = rl.processGoAway(f)
1128+
maybeClose = true
10591129
case *RSTStreamFrame:
10601130
err = rl.processResetStream(f)
1131+
maybeClose = true
10611132
case *SettingsFrame:
10621133
err = rl.processSettings(f)
10631134
case *PushPromiseFrame:
@@ -1072,6 +1143,9 @@ func (rl *clientConnReadLoop) run() error {
10721143
if err != nil {
10731144
return err
10741145
}
1146+
if closeWhenIdle && gotReply && maybeClose && len(rl.activeRes) == 0 {
1147+
cc.closeIfIdle()
1148+
}
10751149
}
10761150
}
10771151

0 commit comments

Comments
 (0)