diff --git a/http2/transport.go b/http2/transport.go index 76a92e0ca..0932a1a23 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -152,19 +152,47 @@ func (t *Transport) pingTimeout() time.Duration { } +// TransportOptions contains options to configure HTTP/2 transport. +type TransportOptions struct { + // 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 +} + +// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. +// The options can be used to configure the HTTP/2 Transport. +// It returns an error if t1 has already been HTTP/2-enabled. +func ConfigureTransportWithOptions(t1 *http.Transport, o *TransportOptions) error { + _, err := configureTransport(t1, o) + return err +} + // ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. // It returns an error if t1 has already been HTTP/2-enabled. func ConfigureTransport(t1 *http.Transport) error { - _, err := configureTransport(t1) + _, err := configureTransport(t1, nil) return err } -func configureTransport(t1 *http.Transport) (*Transport, error) { +func configureTransport(t1 *http.Transport, o *TransportOptions) (*Transport, error) { connPool := new(clientConnPool) t2 := &Transport{ ConnPool: noDialClientConnPool{connPool}, t1: t1, } + if o != nil { + t2.ReadIdleTimeout = o.ReadIdleTimeout + t2.PingTimeout = o.PingTimeout + } connPool.t = t2 if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil { return nil, err diff --git a/http2/transport_test.go b/http2/transport_test.go index ec7493c76..221127589 100644 --- a/http2/transport_test.go +++ b/http2/transport_test.go @@ -32,6 +32,7 @@ import ( "sync/atomic" "testing" "time" + "unsafe" "golang.org/x/net/http2/hpack" ) @@ -594,6 +595,42 @@ func TestTransportDialTLS(t *testing.T) { } } +func TestConfigureTransportWithOptions(t *testing.T) { + t1 := &http.Transport{} + o := &TransportOptions{ReadIdleTimeout: 10 * time.Second, PingTimeout: 10 * time.Second} + err := ConfigureTransportWithOptions(t1, o) + if err != nil { + t.Fatal(err) + } + rf := reflect.ValueOf(t1).Elem().FieldByName("altProto") + rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() + v := rf.Interface().(atomic.Value) + altProto := v.Load().(map[string]http.RoundTripper) + rt := (altProto["https"]).(noDialH2RoundTripper) + t2 := rt.Transport + if e, a := o.ReadIdleTimeout, t2.ReadIdleTimeout; e != a { + t.Errorf("expected ReadIdleTimeout to be %d, got %d", e, a) + } + if e, a := o.PingTimeout, t2.PingTimeout; e != a { + t.Errorf("expected PingTimeout to be %d, got %d", e, a) + } +} + +func TestInternalConfigureTransport(t *testing.T) { + t1 := &http.Transport{} + o := &TransportOptions{ReadIdleTimeout: 10 * time.Second, PingTimeout: 10 * time.Second} + t2, err := configureTransport(t1, o) + if err != nil { + t.Fatal(err) + } + if e, a := o.ReadIdleTimeout, t2.ReadIdleTimeout; e != a { + t.Errorf("expected ReadIdleTimeout to be %d, got %d", e, a) + } + if e, a := o.PingTimeout, t2.PingTimeout; e != a { + t.Errorf("expected PingTimeout to be %d, got %d", e, a) + } +} + func TestConfigureTransport(t *testing.T) { t1 := &http.Transport{} err := ConfigureTransport(t1)