ref!(https): rewrite with latest best practices, for publish

This commit is contained in:
AJ ONeal 2026-08-17 15:09:52 -06:00
parent 71671b7852
commit c66c4a8de4
No known key found for this signature in database
6 changed files with 144 additions and 26 deletions

View File

@ -1,9 +1,9 @@
# https (as in secure)
This wrappers and helpers to supplement Go's built-in `net/http` with reasonable and safe defaults.
This wrappers and helpers to supplement Go's built-in `net/http` with reasonable and safe defaults, even when used with `http`.
See
- [The complete guide to Go net/http timeouts](https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/)
- [Standard net/http config will break your production environment](https://medium.com/@simonfrey/go-as-in-golang-standard-net-http-config-will-break-your-production-environment-1360871cb72b)
- [Don’t use Go’s default HTTP client (in production)](https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779)
- [The complete guide to Go net/http timeouts](https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/)
- [Standard net/http config will break your production environment](https://medium.com/@simonfrey/go-as-in-golang-standard-net-http-config-will-break-your-production-environment-1360871cb72b)
- [Don’t use Go’s default HTTP client (in production)](https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779)

48
https/default.go Normal file
View File

@ -0,0 +1,48 @@
package https
import (
"net"
"net/http"
"time"
)
// NewDefaultDialer expects connections to be established quickly
func NewDefaultDialer() *net.Dialer {
return &net.Dialer{
Timeout: 4 * time.Second,
KeepAlive: 25 * time.Second,
}
}
// NewDefaultTransport expects quick tls termination, but allows for
// sluggish header response times (the server often doesn't know
// what headers to send until the response is nearly ready due to API
// calls, db queries, etc)
func NewDefaultTransport() *http.Transport {
dialer := NewDefaultDialer()
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
ForceAttemptHTTP2: true,
MaxConnsPerHost: 100,
MaxIdleConnsPerHost: 10,
MaxIdleConns: 100,
IdleConnTimeout: 59 * time.Second, // less than 59s firewall timeouts
TLSHandshakeTimeout: 4 * time.Second,
ResponseHeaderTimeout: 20 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
// NewDefaultClient creates a new http client with reasonable and safe defaults
// for API services that are expected to connect and reply relatively quickly.
// DO NOT use for long streaming downloads or extremely slow APIs.
func NewDefaultClient() *http.Client {
return &http.Client{
Timeout: 25 * time.Second,
Transport: NewDefaultTransport(),
}
}

3
https/go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/therootcompany/golib/https
go 1.26.5

View File

@ -1,22 +0,0 @@
package https
import (
"net"
"net/http"
"time"
)
// NewHTTPClient creates a new http client with reasonable and safe defaults
func NewHTTPClient() *http.Client {
transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: 10 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
client := &http.Client{
Timeout: time.Second * 5,
Transport: transport,
}
return client
}

44
https/internal.go Normal file
View File

@ -0,0 +1,44 @@
package https
import (
"net"
"net/http"
"time"
)
// NewInternalDialer is aggressive because we expect very low network latency.
func NewInternalDialer() *net.Dialer {
return &net.Dialer{
Timeout: 2 * time.Second, // plenty for DNS + <200 ms RTT + Happy Eyeballs
KeepAlive: 27 * time.Second,
}
}
// NewInternalTransport prioritizes fast failure and high connection reuse
// for internal service-to-service traffic.
func NewInternalTransport() *http.Transport {
dialer := NewInternalDialer()
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 200,
MaxIdleConnsPerHost: 50, // higher – few hosts, high concurrency
MaxConnsPerHost: 200,
IdleConnTimeout: 29 * time.Second,
TLSHandshakeTimeout: 1 * time.Second,
ResponseHeaderTimeout: 11 * time.Second, // still allows modest backend work
ExpectContinueTimeout: 1 * time.Second,
}
}
// NewInternalClient is for internal APIs that should be fast.
// Do not use for slow batch jobs or external calls.
func NewInternalClient() *http.Client {
return &http.Client{
Timeout: 13 * time.Second,
Transport: NewInternalTransport(),
}
}

45
https/slow.go Normal file
View File

@ -0,0 +1,45 @@
package https
import (
"cmp"
"net"
"net/http"
"time"
)
// NewSlowDialer allows more time for DNS, Happy Eyeballs, and high-latency links.
func NewSlowDialer() *net.Dialer {
return &net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 30 * time.Second,
}
}
// NewSlowTransport is tolerant of slow networks and sluggish servers.
// Still protects against completely hung connections.
func NewSlowTransport() *http.Transport {
dialer := NewSlowDialer()
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
MaxConnsPerHost: 100,
IdleConnTimeout: 89 * time.Second,
TLSHandshakeTimeout: 15 * time.Second,
ResponseHeaderTimeout: 59 * time.Second,
ExpectContinueTimeout: 2 * time.Second,
}
}
// NewSlowClient is for requests that may legitimately take a long time.
// Prefer request-level context timeouts when you need finer control.
func NewSlowClient(timeout time.Duration) *http.Client {
return &http.Client{
Timeout: cmp.Or(timeout, 89*time.Second),
Transport: NewSlowTransport(),
}
}