From 9a04c6f9cecaf64c91a305817f9f80eb1fb6f643 Mon Sep 17 00:00:00 2001 From: Prasad Lohakpure Date: Wed, 22 Apr 2026 11:54:35 +0530 Subject: [PATCH 1/3] HOTFIX: Reuse http client --- internal/pkg/pipeline/task/http/http.go | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/internal/pkg/pipeline/task/http/http.go b/internal/pkg/pipeline/task/http/http.go index a923c72..c6257fe 100644 --- a/internal/pkg/pipeline/task/http/http.go +++ b/internal/pkg/pipeline/task/http/http.go @@ -17,6 +17,8 @@ import ( "github.com/patterninc/caterpillar/internal/pkg/pipeline/task/http/status" ) +const defaultMaxConnsPerHost = 100 + const ( defaultOAuthVersion = `1.0` defaultSignatureMethod = `HMAC-SHA256` @@ -60,6 +62,7 @@ type httpCore struct { Timeout duration.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` MaxRetries int `yaml:"max_retries,omitempty" json:"max_retries,omitempty"` RetryDelay duration.Duration `yaml:"retry_delay,omitempty" json:"retry_delay,omitempty"` + client *http.Client } type result struct { @@ -84,6 +87,19 @@ func New() (task.Task, error) { } +func (h *httpCore) getClient() *http.Client { + if h.client == nil { + h.client = &http.Client{ + Timeout: time.Duration(h.Timeout), + Transport: &http.Transport{ + MaxConnsPerHost: defaultMaxConnsPerHost, + MaxIdleConnsPerHost: defaultMaxConnsPerHost, + }, + } + } + return h.client +} + func (h *httpCore) newFromInput(data []byte) (*httpCore, error) { newHttp := &httpCore{ @@ -102,6 +118,7 @@ func (h *httpCore) newFromInput(data []byte) (*httpCore, error) { Timeout: h.Timeout, MaxRetries: h.MaxRetries, RetryDelay: h.RetryDelay, + client: h.getClient(), } if err := json.Unmarshal(data, newHttp); err != nil { @@ -292,12 +309,7 @@ func (h *httpCore) call(endpoint string) (*result, error) { } } - // Create HTTP client with proxy configuration if specified - client := &http.Client{ - Timeout: time.Duration(h.Timeout), - } - - // Do we use proxy for this one? + client := h.getClient() if h.Proxy != nil { transport, err := h.Proxy.getTransport() if err != nil { @@ -307,7 +319,10 @@ func (h *httpCore) call(endpoint string) (*result, error) { } break } - client.Transport = transport + client = &http.Client{ + Timeout: time.Duration(h.Timeout), + Transport: transport, + } } response, err := client.Do(request) @@ -320,9 +335,8 @@ func (h *httpCore) call(endpoint string) (*result, error) { break } - defer response.Body.Close() - body, err := io.ReadAll(response.Body) + response.Body.Close() if err != nil { lastErr = err if attempt < h.MaxRetries { From 588fb9988bacb7c017214e47d4e29b3c2d7a9365 Mon Sep 17 00:00:00 2001 From: Prasad Lohakpure Date: Wed, 22 Apr 2026 12:06:34 +0530 Subject: [PATCH 2/3] Moving respone close to defer --- internal/pkg/pipeline/task/http/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/pipeline/task/http/http.go b/internal/pkg/pipeline/task/http/http.go index c6257fe..a29001b 100644 --- a/internal/pkg/pipeline/task/http/http.go +++ b/internal/pkg/pipeline/task/http/http.go @@ -334,9 +334,9 @@ func (h *httpCore) call(endpoint string) (*result, error) { } break } + defer response.Body.Close() body, err := io.ReadAll(response.Body) - response.Body.Close() if err != nil { lastErr = err if attempt < h.MaxRetries { From 67b5dab99411dfea9665766f2591d45b098b4acb Mon Sep 17 00:00:00 2001 From: Prasad Lohakpure Date: Wed, 22 Apr 2026 15:42:45 +0530 Subject: [PATCH 3/3] Changes as per co-pilot suggestions --- internal/pkg/pipeline/task/http/http.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/pkg/pipeline/task/http/http.go b/internal/pkg/pipeline/task/http/http.go index a29001b..33df7e1 100644 --- a/internal/pkg/pipeline/task/http/http.go +++ b/internal/pkg/pipeline/task/http/http.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "strings" + "sync" "time" "github.com/patterninc/caterpillar/internal/pkg/config" @@ -63,6 +64,7 @@ type httpCore struct { MaxRetries int `yaml:"max_retries,omitempty" json:"max_retries,omitempty"` RetryDelay duration.Duration `yaml:"retry_delay,omitempty" json:"retry_delay,omitempty"` client *http.Client + clientOnce sync.Once } type result struct { @@ -88,15 +90,15 @@ func New() (task.Task, error) { } func (h *httpCore) getClient() *http.Client { - if h.client == nil { + h.clientOnce.Do(func() { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.MaxConnsPerHost = defaultMaxConnsPerHost + transport.MaxIdleConnsPerHost = defaultMaxConnsPerHost h.client = &http.Client{ - Timeout: time.Duration(h.Timeout), - Transport: &http.Transport{ - MaxConnsPerHost: defaultMaxConnsPerHost, - MaxIdleConnsPerHost: defaultMaxConnsPerHost, - }, + Timeout: time.Duration(h.Timeout), + Transport: transport, } - } + }) return h.client } @@ -118,7 +120,6 @@ func (h *httpCore) newFromInput(data []byte) (*httpCore, error) { Timeout: h.Timeout, MaxRetries: h.MaxRetries, RetryDelay: h.RetryDelay, - client: h.getClient(), } if err := json.Unmarshal(data, newHttp); err != nil {