From e0a81170a35b9b0891db7add0cd64ea5ecd67c2b Mon Sep 17 00:00:00 2001 From: shedybaba Date: Fri, 6 Mar 2026 22:48:36 +0100 Subject: [PATCH] fix: add HTTP client timeouts and improve error handling in publish/subscribe/list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace http.DefaultClient (zero timeout) with a shared httpClient configured with a 30-second timeout across publish, subscribe, and list-topics commands. This prevents the CLI from hanging indefinitely when the remote server is unreachable or slow to respond. Other commands in the codebase (health, tracer, update) already use properly configured HTTP clients — this aligns the remaining commands with the established pattern. Additional fixes in the same paths: - Use bytes.NewReader instead of strings.NewReader(string(...)) in publish to avoid an unnecessary heap allocation on every request - Wrap the bare error return in publish HTTP request creation with descriptive context (consistent with all other error returns) - Check io.ReadAll errors in publish and subscribe instead of silently discarding them, which previously produced empty error messages --- cmd/list.go | 4 ++-- cmd/publish.go | 12 ++++++++---- cmd/subscribe.go | 11 +++++++---- cmd/utils.go | 7 +++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/cmd/list.go b/cmd/list.go index 000c83b..4957d60 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -54,7 +54,7 @@ This command shows your active topics and their count.`, req.Header.Set("Content-Type", "application/json") // Execute the request - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("HTTP GET request failed: %v", err) } @@ -146,7 +146,7 @@ This command shows your active topics and their count.`, req.Header.Set("Content-Type", "application/json") // Execute the request - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("HTTP GET request failed: %v", err) } diff --git a/cmd/publish.go b/cmd/publish.go index 34ea667..80f74e4 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -1,6 +1,7 @@ package cmd import ( + "bytes" "context" "crypto/sha256" "encoding/hex" @@ -206,9 +207,9 @@ var publishCmd = &cobra.Command{ } url := baseURL + "/api/v1/publish" - req, err := http.NewRequest("POST", url, strings.NewReader(string(reqBytes))) + req, err := http.NewRequest("POST", url, bytes.NewReader(reqBytes)) if err != nil { - return err + return fmt.Errorf("failed to create publish request: %v", err) } // Only set Authorization header if auth is enabled if !IsAuthDisabled() && token != nil { @@ -216,12 +217,15 @@ var publishCmd = &cobra.Command{ } req.Header.Set("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("HTTP publish failed: %v", err) } defer resp.Body.Close() //nolint:errcheck - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read publish response: %v", err) + } if resp.StatusCode != 200 { return fmt.Errorf("publish error: %s", string(body)) } diff --git a/cmd/subscribe.go b/cmd/subscribe.go index 9e7fa66..2d70d31 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -226,13 +226,16 @@ var subscribeCmd = &cobra.Command{ } req.Header.Set("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("HTTP POST subscribe failed: %v", err) } defer resp.Body.Close() //nolint:errcheck - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read subscribe response: %v", err) + } if resp.StatusCode != 200 { return fmt.Errorf("HTTP POST subscribe error: %s", string(body)) @@ -291,7 +294,7 @@ var subscribeCmd = &cobra.Command{ return } req.Header.Set("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { fmt.Printf("Webhook request error: %v\n", err) return @@ -404,7 +407,7 @@ var subscribeCmd = &cobra.Command{ } req.Header.Set("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { fmt.Printf("Webhook request error: %v\n", err) return diff --git a/cmd/utils.go b/cmd/utils.go index e6453ac..f759586 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1,9 +1,16 @@ package cmd import ( + "net/http" "regexp" + "time" ) +// httpClient is a shared HTTP client with a sensible timeout. +// Use this instead of http.DefaultClient to prevent indefinite hangs +// when the remote server is unreachable or slow to respond. +var httpClient = &http.Client{Timeout: 30 * time.Second} + // extractIPFromURL extracts IP address from URL string func extractIPFromURL(url string) string { ipRegex := regexp.MustCompile(`\d+\.\d+\.\d+\.\d+`)