From b1abc87365472b8a07393d7e8f0ff6cdc561ce6c Mon Sep 17 00:00:00 2001 From: Seth Hoenig Date: Tue, 17 Feb 2026 08:47:23 -0600 Subject: [PATCH] httpclient: a common http.Client with sensible default values --- README.md | 9 +++++++++ httpclient/client.go | 36 ++++++++++++++++++++++++++++++++++++ httpclient/client_test.go | 15 +++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 httpclient/client.go create mode 100644 httpclient/client_test.go diff --git a/README.md b/README.md index 6a9645b..b659071 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,15 @@ type Sessions[I identity.UserIdentity] interface { } ``` +#### package webtools/httpclient + +Provides a sharable `http.Client` that sets sensible values for maintaining a +pool of idle connections. + +```go +client := httpclient.Get() +``` + #### package webtools/middles/identity Provides a set of generic structs used for marshaling identity. The interfaces diff --git a/httpclient/client.go b/httpclient/client.go new file mode 100644 index 0000000..c318741 --- /dev/null +++ b/httpclient/client.go @@ -0,0 +1,36 @@ +package httpclient + +import ( + "net" + "net/http" + "time" +) + +func pooledTransport() *http.Transport { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 32, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 3 * time.Second, + ForceAttemptHTTP2: true, + MaxIdleConnsPerHost: 1, + } + return transport +} + +var client = &http.Client{ + Timeout: 1 * time.Minute, + Transport: pooledTransport(), +} + +// Get returns an http.Client tuned for shared / pooled connections, with +// reasonable values for idle connections, timeouts, and http2 usage. +func Get() *http.Client { + return client +} diff --git a/httpclient/client_test.go b/httpclient/client_test.go new file mode 100644 index 0000000..fa37834 --- /dev/null +++ b/httpclient/client_test.go @@ -0,0 +1,15 @@ +package httpclient + +import ( + "testing" + "time" + + "github.com/shoenig/test/must" +) + +func TestGet(t *testing.T) { + t.Parallel() + + c := Get() + must.Eq(t, 1*time.Minute, c.Timeout) +}