Repository: prometheus/common
Pull request #308 (2021-06-08 18:37:57)

Add WithIdleConnTimeout HTTP client option
This change enables HTTP long-polling SD mechanisms to configure the idle connection timeout so that they can use this config lib to create HTTP clients.

For example, the `consul_sd` manually creates a `http.Transport`: 
https://github.com/prometheus/prometheus/blob/7bc11dcb06640ce22b4e15eb52b2c065f97cf79a/discovery/consul/consul.go#L191-L198



Patch:
diff --git a/config/http_config.go b/config/http_config.go
index 43f2967..8050258 100644
--- a/config/http_config.go
+++ b/config/http_config.go
@@ -47,6 +47,9 @@ var DefaultHTTPClientConfig = HTTPClientConfig{
 var defaultHTTPClientOptions = httpClientOptions{
 	keepAlivesEnabled: true,
 	http2Enabled:      true,
+	// 5 minutes is typically above the maximum sane scrape interval. So we can
+	// use keepalive for all configurations.
+	idleConnTimeout: 5 * time.Minute,
 }
 
 type closeIdler interface {
@@ -283,6 +286,7 @@ type httpClientOptions struct {
 	dialContextFunc   DialContextFunc
 	keepAlivesEnabled bool
 	http2Enabled      bool
+	idleConnTimeout   time.Duration
 }
 
 // HTTPClientOption defines an option that can be applied to the HTTP client.
@@ -309,6 +313,13 @@ func WithHTTP2Disabled() HTTPClientOption {
 	}
 }
 
+// WithIdleConnTimeout allows setting the idle connection timeout.
+func WithIdleConnTimeout(timeout time.Duration) HTTPClientOption {
+	return func(opts *httpClientOptions) {
+		opts.idleConnTimeout = timeout
+	}
+}
+
 // NewClient returns a http.Client using the specified http.RoundTripper.
 func newClient(rt http.RoundTripper) *http.Client {
 	return &http.Client{Transport: rt}
@@ -357,15 +368,13 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
 		// The only timeout we care about is the configured scrape timeout.
 		// It is applied on request. So we leave out any timings here.
 		var rt http.RoundTripper = &http.Transport{
-			Proxy:               http.ProxyURL(cfg.ProxyURL.URL),
-			MaxIdleConns:        20000,
-			MaxIdleConnsPerHost: 1000, // see https://github.com/golang/go/issues/13801
-			DisableKeepAlives:   !opts.keepAlivesEnabled,
-			TLSClientConfig:     tlsConfig,
-			DisableCompression:  true,
-			// 5 minutes is typically above the maximum sane scrape interval. So we can
-			// use keepalive for all configurations.
-			IdleConnTimeout:       5 * time.Minute,
+			Proxy:                 http.ProxyURL(cfg.ProxyURL.URL),
+			MaxIdleConns:          20000,
+			MaxIdleConnsPerHost:   1000, // see https://github.com/golang/go/issues/13801
+			DisableKeepAlives:     !opts.keepAlivesEnabled,
+			TLSClientConfig:       tlsConfig,
+			DisableCompression:    true,
+			IdleConnTimeout:       opts.idleConnTimeout,
 			TLSHandshakeTimeout:   10 * time.Second,
 			ExpectContinueTimeout: 1 * time.Second,
 			DialContext:           dialContext,


Test patch:
diff --git a/config/http_config_test.go b/config/http_config_test.go
index f1b8ce0..49b0aa3 100644
--- a/config/http_config_test.go
+++ b/config/http_config_test.go
@@ -456,6 +456,25 @@ func TestCustomDialContextFunc(t *testing.T) {
 	}
 }
 
+func TestCustomIdleConnTimeout(t *testing.T) {
+	timeout := time.Second * 5
+
+	cfg := HTTPClientConfig{}
+	rt, err := NewRoundTripperFromConfig(cfg, "test", WithIdleConnTimeout(timeout))
+	if err != nil {
+		t.Fatalf("Can't create a round-tripper from this config: %+v", cfg)
+	}
+
+	transport, ok := rt.(*http.Transport)
+	if !ok {
+		t.Fatalf("Unexpected transport: %+v", transport)
+	}
+
+	if transport.IdleConnTimeout != timeout {
+		t.Fatalf("Unexpected idle connection timeout: %+v", timeout)
+	}
+}
+
 func TestMissingBearerAuthFile(t *testing.T) {
 	cfg := HTTPClientConfig{
 		BearerTokenFile: MissingBearerTokenFile,