Replace usage of http.DefaultClient and http.DefaultTransport (#104135)

Remove usage of http.DefaultClient and http.DefaultTransport

Part of grafana/data-sources#484
This commit is contained in:
beejeebus
2025-05-09 13:26:39 -04:00
committed by GitHub
parent 8f4b2bbece
commit 8f79e4882f
18 changed files with 120 additions and 44 deletions
+3
View File
@@ -2270,6 +2270,9 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/
github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw=
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI=
github.com/grafana/grafana-plugin-sdk-go v0.275.0/go.mod h1:mO9LJqdXDh5JpO/xIdPAeg5LdThgQ06Y/SLpXDWKw2c=
github.com/grafana/grafana-plugin-sdk-go v0.277.0 h1:VDU2F4Y5NeRS//ejctdZtsAshrGaEdbtW33FsK0EQss=
github.com/grafana/grafana-plugin-sdk-go v0.277.0/go.mod h1:mAUWg68w5+1f5TLDqagIr8sWr1RT9h7ufJl5NMcWJAU=
github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA=
github.com/grafana/grafana/apps/advisor v0.0.0-20250220154326-6e5de80ef295/go.mod h1:9I1dKV3Dqr0NPR9Af0WJGxOytp5/6W3JLiNChOz8r+c=
github.com/grafana/grafana/apps/advisor v0.0.0-20250506052906-7a2fc797fb4a/go.mod h1:xOL9buMMbQg+3m0jPfrza4/5iwe4EBrnur/aJGAA1pM=
+9 -18
View File
@@ -16,20 +16,6 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func setClientWithoutRedirectFollow(t *testing.T) {
t.Helper()
old := http.DefaultClient
http.DefaultClient = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
t.Cleanup(func() {
http.DefaultClient = old
})
}
func TestOAuthLogin_Redirect(t *testing.T) {
type testCase struct {
desc string
@@ -79,7 +65,9 @@ func TestOAuthLogin_Redirect(t *testing.T) {
})
// we need to prevent the http.Client from following redirects
setClientWithoutRedirectFollow(t)
server.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
res, err := server.Send(server.NewGetRequest("/login/generic_oauth"))
require.NoError(t, err)
@@ -155,7 +143,9 @@ func TestOAuthLogin_AuthorizationCode(t *testing.T) {
})
// we need to prevent the http.Client from following redirects
setClientWithoutRedirectFollow(t)
server.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
res, err := server.Send(server.NewGetRequest("/login/generic_oauth?code=code"))
require.NoError(t, err)
@@ -198,8 +188,9 @@ func TestOAuthLogin_Error(t *testing.T) {
hs.log = log.NewNopLogger()
hs.SecretsService = fakes.NewFakeSecretsService()
})
setClientWithoutRedirectFollow(t)
server.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
res, err := server.Send(server.NewGetRequest("/login/azuread?error=someerror"))
require.NoError(t, err)
+3 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/httpclient"
)
type ManifestInfo struct {
@@ -36,6 +37,7 @@ type EntryPointInfo struct {
var (
entryPointAssetsCacheMu sync.RWMutex // guard entryPointAssetsCache
entryPointAssetsCache *dtos.EntryPointAssets // TODO: get rid of global state
httpClient = httpclient.New()
)
func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licensing) (*dtos.EntryPointAssets, error) {
@@ -88,7 +90,7 @@ func readWebAssetsFromCDN(ctx context.Context, baseURL string) (*dtos.EntryPoint
if err != nil {
return nil, err
}
response, err := http.DefaultClient.Do(req)
response, err := httpClient.Do(req)
if err != nil {
return nil, err
}
+20 -1
View File
@@ -2,10 +2,12 @@ package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
@@ -26,6 +28,23 @@ import (
const grafanaAPI = "https://grafana.com/api"
var httpClient = http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {
return dialer.DialContext
}(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
// GrafanaCom implements the sub-command "grafana-com".
func GrafanaCom(c *cli.Context) error {
bucketStr := c.String("src-bucket")
@@ -330,7 +349,7 @@ func postRequest(cfg packaging.PublishConfig, pth string, body any, descr string
return nil
}
resp, err := http.DefaultClient.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, err)
}
@@ -22,6 +22,7 @@ import (
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/httpclient"
)
type AzureBlobUploader struct {
@@ -175,7 +176,7 @@ func (c *StorageClient) transport() http.RoundTripper {
if c.Transport != nil {
return c.Transport
}
return http.DefaultTransport
return httpclient.NewHTTPTransport()
}
func NewStorageClient(account, accessKey string) *StorageClient {
@@ -82,7 +82,7 @@ func New(cfg *setting.Cfg, validator validations.DataSourceRequestURLValidator,
})
}
// newConntrackRoundTripper takes a http.DefaultTransport and adds the Conntrack Dialer
// newConntrackRoundTripper takes a http.Transport and adds the Conntrack Dialer
// so we can instrument outbound connections
func newConntrackRoundTripper(name string, transport *http.Transport) *http.Transport {
transport.DialContext = conntrack.NewDialContextFunc(
@@ -5,6 +5,8 @@ import (
"io"
"net/http"
"sync/atomic"
"github.com/grafana/grafana/pkg/util/httpclient"
)
var errBytesLimitExceeded = fmt.Errorf("bytes limit exceeded")
@@ -17,10 +19,10 @@ type ByteLimitedTransport struct {
}
// NewByteLimitedTransport creates a new ByteLimitedTransport with the specified transport and byte limit.
// If transport is nil, http.DefaultTransport will be used.
// If transport is nil, a new http.Transport modeled after http.DefaultTransport will be used.
func NewByteLimitedTransport(transport http.RoundTripper, limit int64) *ByteLimitedTransport {
if transport == nil {
transport = http.DefaultTransport
transport = httpclient.NewHTTPTransport()
}
return &ByteLimitedTransport{
Transport: transport,
@@ -47,7 +47,8 @@ func TestNewByteLimitedTransport(t *testing.T) {
assert.Equal(t, int64(0), blt.Bytes)
if tt.transport == nil {
assert.Equal(t, http.DefaultTransport, blt.Transport)
assert.NotNil(t, blt.Transport)
assert.NotEqual(t, http.DefaultTransport, blt.Transport)
} else {
assert.Equal(t, tt.transport, blt.Transport)
}
@@ -26,6 +26,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/grafana/grafana/pkg/util/httpclient"
)
const (
@@ -36,7 +37,7 @@ const (
func init() {
// Create a size-limited writer that will cancel the context if size is exceeded
limitedTransport := NewByteLimitedTransport(http.DefaultTransport, maxOperationBytes)
limitedTransport := NewByteLimitedTransport(httpclient.NewHTTPTransport(), maxOperationBytes)
httpClient := githttp.NewClient(&http.Client{
Transport: limitedTransport,
})
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/httpclient"
)
type gcomLogoutRequest struct {
@@ -48,7 +49,7 @@ func (s *GComSSOService) LogoutHook(ctx context.Context, user identity.Requester
hReq.Header.Add("Content-Type", "application/json")
hReq.Header.Add("Authorization", "Bearer "+s.cfg.GrafanaComSSOAPIToken)
c := http.DefaultClient
c := httpclient.New()
resp, err := c.Do(hReq)
if err != nil {
s.logger.Error("failed to send request", "error", err)
+5 -3
View File
@@ -12,6 +12,7 @@ import (
"github.com/centrifugal/centrifuge"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/util/httpclient"
"github.com/grafana/grafana/pkg/services/live/managedstream"
)
@@ -63,24 +64,25 @@ func postTestData() {
}
jsonData, _ := json.Marshal(d)
log.Println(string(jsonData))
httpClient := httpclient.New()
req, _ := http.NewRequest("POST", "http://localhost:3000/api/live/pipeline/push/stream/json/auto", bytes.NewReader(jsonData))
req.Header.Set("Authorization", "Bearer "+os.Getenv("GF_TOKEN"))
resp, err := http.DefaultClient.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
log.Fatal(err)
}
_ = resp.Body.Close()
req, _ = http.NewRequest("POST", "http://localhost:3000/api/live/push/pipeline/push/stream/json/tip", bytes.NewReader(jsonData))
req.Header.Set("Authorization", "Bearer "+os.Getenv("GF_TOKEN"))
resp, err = http.DefaultClient.Do(req)
resp, err = httpClient.Do(req)
if err != nil {
log.Fatal(err)
}
_ = resp.Body.Close()
req, _ = http.NewRequest("POST", "http://localhost:3000/api/live/pipeline/push/stream/json/exact", bytes.NewReader(jsonData))
req.Header.Set("Authorization", "Bearer "+os.Getenv("GF_TOKEN"))
resp, err = http.DefaultClient.Do(req)
resp, err = httpClient.Do(req)
if err != nil {
log.Fatal(err)
}
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/ngalert/client"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/util/httpclient"
amclient "github.com/prometheus/alertmanager/api/v2/client"
)
@@ -37,7 +38,7 @@ func NewAlertmanager(cfg *AlertmanagerConfig, metrics *metrics.RemoteAlertmanage
c := &http.Client{Transport: &MimirAuthRoundTripper{
TenantID: cfg.TenantID,
Password: cfg.Password,
Next: http.DefaultTransport,
Next: httpclient.NewHTTPTransport(),
}}
tc := client.NewTimedClient(c, metrics.RequestLatency)
+2 -1
View File
@@ -19,6 +19,7 @@ import (
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/client"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/util/httpclient"
)
// MimirClient contains all the methods to query the migration critical endpoints of Mimir instance, it's an interface to allow multiple implementations.
@@ -86,7 +87,7 @@ func New(cfg *Config, metrics *metrics.RemoteAlertmanager, tracer tracing.Tracer
rt := &MimirAuthRoundTripper{
TenantID: cfg.TenantID,
Password: cfg.Password,
Next: http.DefaultTransport,
Next: httpclient.NewHTTPTransport(),
}
c := &http.Client{
+2 -1
View File
@@ -31,6 +31,7 @@ import (
"time"
"github.com/go-openapi/strfmt"
"github.com/grafana/grafana/pkg/util/httpclient"
"github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/client_golang/prometheus"
config_util "github.com/prometheus/common/config"
@@ -216,7 +217,7 @@ func newAlertMetrics(r prometheus.Registerer, queueCap int, queueLen, alertmanag
func do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
client = httpclient.New()
}
return client.Do(req.WithContext(ctx))
}
+1 -9
View File
@@ -3,7 +3,6 @@ package cloudwatch
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
@@ -324,14 +323,7 @@ func (ds *DataSource) newSession(region string) (*session.Session, error) {
// only update the transport to try to avoid the issue mentioned here https://github.com/grafana/grafana/issues/46365
// also, 'sess' is cached and reused, so the first time it might have the transport not set, the following uses it will
if sess.Config.HTTPClient.Transport == nil {
// following go standard library logic (https://pkg.go.dev/net/http#Client), if no Transport is provided,
// then we use http.DefaultTransport
defTransport, ok := http.DefaultTransport.(*http.Transport)
if !ok {
// this should not happen but validating just in case
return nil, errors.New("default http client transport is not of type http.Transport")
}
sess.Config.HTTPClient.Transport = defTransport.Clone()
sess.Config.HTTPClient.Transport = httpclient.NewHTTPTransport()
}
err = proxy.New(ds.ProxyOpts).ConfigureSecureSocksHTTPProxy(sess.Config.HTTPClient.Transport.(*http.Transport))
if err != nil {
+36
View File
@@ -0,0 +1,36 @@
package httpclient
import (
"context"
"net"
"net/http"
"time"
)
// New creates a new http.Client.
func New() *http.Client {
return &http.Client{
Transport: NewHTTPTransport(),
}
}
// NewHTTPTransport returns a new HTTP Transport, based off the definition in
// the stdlib http.DefaultTransport. It's not a clone, because that would return
// any mutations of http.DefaultTransport from other code at the time of the call.
// Any plugin that needs a default http transport should use this function.
func NewHTTPTransport() http.RoundTripper {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {
return dialer.DialContext
}(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
+4 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util/httpclient"
"github.com/grafana/grafana/pkg/web"
)
@@ -24,6 +25,7 @@ type Server struct {
Mux *web.Mux
RouteRegister routing.RouteRegister
TestServer *httptest.Server
HttpClient *http.Client
}
// NewServer starts and returns a new server.
@@ -50,6 +52,7 @@ func NewServer(t testing.TB, routeRegister routing.RouteRegister) *Server {
RouteRegister: routeRegister,
Mux: m,
TestServer: testServer,
HttpClient: httpclient.New(),
}
}
@@ -81,7 +84,7 @@ func (s *Server) NewRequest(method string, target string, body io.Reader) *http.
// Send sends a HTTP request to the test server and returns an HTTP response.
func (s *Server) Send(req *http.Request) (*http.Response, error) {
return http.DefaultClient.Do(req)
return s.HttpClient.Do(req)
}
// SendJSON sets the Content-Type header to application/json and sends
+20 -1
View File
@@ -2,15 +2,34 @@ package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
"time"
)
var httpClient = http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {
return dialer.DialContext
}(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
type publisher struct {
apiKey string
apiURI string
@@ -264,7 +283,7 @@ func (p *publisher) postRequest(url string, obj any, desc string) error {
req.Header.Add("Authorization", "Bearer "+p.apiKey)
req.Header.Add("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
res, err := httpClient.Do(req)
if err != nil {
return err
}