From aac28c5f738f60fa9c7c8b2b2337733e0bf3c3c1 Mon Sep 17 00:00:00 2001 From: Marc Campbell Date: Tue, 13 Jan 2026 14:37:06 -0600 Subject: [PATCH 1/2] enable to handle wildcard.localhost dns --- pkg/platformclient/app.go | 2 +- pkg/platformclient/channel.go | 2 +- pkg/platformclient/client.go | 41 ++++++++++++++++++++++++++++++++--- pkg/platformclient/release.go | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/pkg/platformclient/app.go b/pkg/platformclient/app.go index b8fb56581..a4a81427e 100644 --- a/pkg/platformclient/app.go +++ b/pkg/platformclient/app.go @@ -52,7 +52,7 @@ func (c *HTTPClient) DeleteApp(id string) error { return err } req.Header.Add("Authorization", c.apiKey) - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("DeleteApp (%s %s): %w", req.Method, endpoint, err) } diff --git a/pkg/platformclient/channel.go b/pkg/platformclient/channel.go index 81ecdb847..3338dc142 100644 --- a/pkg/platformclient/channel.go +++ b/pkg/platformclient/channel.go @@ -60,7 +60,7 @@ func (c *HTTPClient) ArchiveChannel(appID, channelID string) error { return err } req.Header.Add("Authorization", c.apiKey) - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("ArchiveChannel (%s %s): %w", req.Method, endpoint, err) } diff --git a/pkg/platformclient/client.go b/pkg/platformclient/client.go index 55b32dae0..64da01773 100644 --- a/pkg/platformclient/client.go +++ b/pkg/platformclient/client.go @@ -7,8 +7,11 @@ import ( "encoding/json" "fmt" "io" + "net" "net/http" "os" + "strings" + "time" "github.com/pkg/errors" "github.com/replicatedhq/replicated/pkg/version" @@ -20,6 +23,38 @@ var ( ErrForbidden = errors.New("the action is not allowed for the current user or team") ) +// httpClient is a custom HTTP client that handles .localhost domains properly. +// Go's default DNS resolver doesn't handle .localhost domains like browsers do +// (per RFC 6761), so we need custom logic to resolve them to 127.0.0.1. +var httpClient = &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + // Check if the address is a .localhost domain + host, port, err := net.SplitHostPort(addr) + if err != nil { + // If there's no port, just use the address as host + host = addr + port = "" + } + + // If the host ends with .localhost, replace it with 127.0.0.1 + if strings.HasSuffix(host, ".localhost") { + if port != "" { + addr = net.JoinHostPort("127.0.0.1", port) + } else { + addr = "127.0.0.1" + } + } + + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + } + return dialer.DialContext(ctx, network, addr) + }, + }, +} + type APIError struct { Method string Endpoint string @@ -83,7 +118,7 @@ func (c *HTTPClient) DoJSONWithoutUnmarshal(method string, path string, reqBody req.Header.Set("Authorization", c.apiKey) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -133,7 +168,7 @@ func (c *HTTPClient) DoJSON(ctx context.Context, method string, path string, suc return errors.Wrap(err, "add github actions headers") } - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -222,7 +257,7 @@ func (c *HTTPClient) HTTPGet(path string, successStatus int) ([]byte, error) { } req.Header.Set("Authorization", c.apiKey) - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } diff --git a/pkg/platformclient/release.go b/pkg/platformclient/release.go index 35e25734f..4e8655f8e 100644 --- a/pkg/platformclient/release.go +++ b/pkg/platformclient/release.go @@ -49,7 +49,7 @@ func (c *HTTPClient) UpdateRelease(appID string, sequence int64, yaml string) er } req.Header.Set("Authorization", c.apiKey) req.Header.Set("Content-Type", "application/yaml") - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("UpdateRelease: %w", err) } From 417db28c489ce1c630bb3330b1ab5739005674c3 Mon Sep 17 00:00:00 2001 From: Marc Campbell Date: Tue, 13 Jan 2026 14:37:19 -0600 Subject: [PATCH 2/2] enable to handle wildcard.localhost dns --- pkg/platformclient/client.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/platformclient/client.go b/pkg/platformclient/client.go index 64da01773..db3f5c0cf 100644 --- a/pkg/platformclient/client.go +++ b/pkg/platformclient/client.go @@ -26,6 +26,7 @@ var ( // httpClient is a custom HTTP client that handles .localhost domains properly. // Go's default DNS resolver doesn't handle .localhost domains like browsers do // (per RFC 6761), so we need custom logic to resolve them to 127.0.0.1. +// This is a singleton that's reused for all requests to avoid leaking connections. var httpClient = &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { @@ -52,6 +53,11 @@ var httpClient = &http.Client{ } return dialer.DialContext(ctx, network, addr) }, + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, }, }