|
@@ -108,6 +108,19 @@ type Transport struct {
|
|
|
// waiting for their turn.
|
|
// waiting for their turn.
|
|
|
StrictMaxConcurrentStreams bool
|
|
StrictMaxConcurrentStreams bool
|
|
|
|
|
|
|
|
|
|
+ // ReadIdleTimeout is the timeout after which a health check using ping
|
|
|
|
|
+ // frame will be carried out if no frame is received on the connection.
|
|
|
|
|
+ // Note that a ping response will is considered a received frame, so if
|
|
|
|
|
+ // there is no other traffic on the connection, the health check will
|
|
|
|
|
+ // be performed every ReadIdleTimeout interval.
|
|
|
|
|
+ // If zero, no health check is performed.
|
|
|
|
|
+ ReadIdleTimeout time.Duration
|
|
|
|
|
+
|
|
|
|
|
+ // PingTimeout is the timeout after which the connection will be closed
|
|
|
|
|
+ // if a response to Ping is not received.
|
|
|
|
|
+ // Defaults to 15s.
|
|
|
|
|
+ PingTimeout time.Duration
|
|
|
|
|
+
|
|
|
// t1, if non-nil, is the standard library Transport using
|
|
// t1, if non-nil, is the standard library Transport using
|
|
|
// this transport. Its settings are used (but not its
|
|
// this transport. Its settings are used (but not its
|
|
|
// RoundTrip method, etc).
|
|
// RoundTrip method, etc).
|
|
@@ -131,6 +144,14 @@ func (t *Transport) disableCompression() bool {
|
|
|
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
|
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (t *Transport) pingTimeout() time.Duration {
|
|
|
|
|
+ if t.PingTimeout == 0 {
|
|
|
|
|
+ return 15 * time.Second
|
|
|
|
|
+ }
|
|
|
|
|
+ return t.PingTimeout
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
|
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
|
|
// It returns an error if t1 has already been HTTP/2-enabled.
|
|
// It returns an error if t1 has already been HTTP/2-enabled.
|
|
|
func ConfigureTransport(t1 *http.Transport) error {
|
|
func ConfigureTransport(t1 *http.Transport) error {
|
|
@@ -675,6 +696,20 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
|
|
return cc, nil
|
|
return cc, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (cc *ClientConn) healthCheck() {
|
|
|
|
|
+ pingTimeout := cc.t.pingTimeout()
|
|
|
|
|
+ // We don't need to periodically ping in the health check, because the readLoop of ClientConn will
|
|
|
|
|
+ // trigger the healthCheck again if there is no frame received.
|
|
|
|
|
+ ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
|
|
|
|
|
+ defer cancel()
|
|
|
|
|
+ err := cc.Ping(ctx)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ cc.closeForLostPing()
|
|
|
|
|
+ cc.t.connPool().MarkDead(cc)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
|
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
|
|
cc.mu.Lock()
|
|
cc.mu.Lock()
|
|
|
defer cc.mu.Unlock()
|
|
defer cc.mu.Unlock()
|
|
@@ -846,14 +881,12 @@ func (cc *ClientConn) sendGoAway() error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Close closes the client connection immediately.
|
|
|
|
|
-//
|
|
|
|
|
-// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
|
|
|
|
-func (cc *ClientConn) Close() error {
|
|
|
|
|
|
|
+// closes the client connection immediately. In-flight requests are interrupted.
|
|
|
|
|
+// err is sent to streams.
|
|
|
|
|
+func (cc *ClientConn) closeForError(err error) error {
|
|
|
cc.mu.Lock()
|
|
cc.mu.Lock()
|
|
|
defer cc.cond.Broadcast()
|
|
defer cc.cond.Broadcast()
|
|
|
defer cc.mu.Unlock()
|
|
defer cc.mu.Unlock()
|
|
|
- err := errors.New("http2: client connection force closed via ClientConn.Close")
|
|
|
|
|
for id, cs := range cc.streams {
|
|
for id, cs := range cc.streams {
|
|
|
select {
|
|
select {
|
|
|
case cs.resc <- resAndError{err: err}:
|
|
case cs.resc <- resAndError{err: err}:
|
|
@@ -866,6 +899,20 @@ func (cc *ClientConn) Close() error {
|
|
|
return cc.tconn.Close()
|
|
return cc.tconn.Close()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Close closes the client connection immediately.
|
|
|
|
|
+//
|
|
|
|
|
+// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
|
|
|
|
+func (cc *ClientConn) Close() error {
|
|
|
|
|
+ err := errors.New("http2: client connection force closed via ClientConn.Close")
|
|
|
|
|
+ return cc.closeForError(err)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// closes the client connection immediately. In-flight requests are interrupted.
|
|
|
|
|
+func (cc *ClientConn) closeForLostPing() error {
|
|
|
|
|
+ err := errors.New("http2: client connection lost")
|
|
|
|
|
+ return cc.closeForError(err)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const maxAllocFrameSize = 512 << 10
|
|
const maxAllocFrameSize = 512 << 10
|
|
|
|
|
|
|
|
// frameBuffer returns a scratch buffer suitable for writing DATA frames.
|
|
// frameBuffer returns a scratch buffer suitable for writing DATA frames.
|
|
@@ -1737,8 +1784,17 @@ func (rl *clientConnReadLoop) run() error {
|
|
|
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
|
|
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
|
|
|
gotReply := false // ever saw a HEADERS reply
|
|
gotReply := false // ever saw a HEADERS reply
|
|
|
gotSettings := false
|
|
gotSettings := false
|
|
|
|
|
+ readIdleTimeout := cc.t.ReadIdleTimeout
|
|
|
|
|
+ var t *time.Timer
|
|
|
|
|
+ if readIdleTimeout != 0 {
|
|
|
|
|
+ t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
|
|
|
|
|
+ defer t.Stop()
|
|
|
|
|
+ }
|
|
|
for {
|
|
for {
|
|
|
f, err := cc.fr.ReadFrame()
|
|
f, err := cc.fr.ReadFrame()
|
|
|
|
|
+ if t != nil {
|
|
|
|
|
+ t.Reset(readIdleTimeout)
|
|
|
|
|
+ }
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
|
|
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
|
|
|
}
|
|
}
|