summaryrefslogtreecommitdiff
path: root/internal/httpclient
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-11 18:28:41 +0200
committerPaul Buetow <paul@buetow.org>2026-03-11 18:28:41 +0200
commit9706e53620045c20bf732054a286183c70f77581 (patch)
tree166e822e9d41b0cc980d4481c5c175ec7164a107 /internal/httpclient
parent1c12325c64bb734dd0ae95a0c803de1ff45d2b4c (diff)
fix(api): add timed shared HTTP client
Diffstat (limited to 'internal/httpclient')
-rw-r--r--internal/httpclient/client.go30
-rw-r--r--internal/httpclient/client_test.go44
2 files changed, 74 insertions, 0 deletions
diff --git a/internal/httpclient/client.go b/internal/httpclient/client.go
new file mode 100644
index 0000000..5c57cdc
--- /dev/null
+++ b/internal/httpclient/client.go
@@ -0,0 +1,30 @@
+package httpclient
+
+import (
+ "context"
+ "io"
+ "net/http"
+ "time"
+)
+
+const DefaultTimeout = 30 * time.Second
+
+var defaultClient = &http.Client{
+ Timeout: DefaultTimeout,
+}
+
+func Do(req *http.Request) (*http.Response, error) {
+ return defaultClient.Do(req)
+}
+
+func NewRequest(method, url string, body io.Reader) (*http.Request, context.CancelFunc, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
+
+ req, err := http.NewRequestWithContext(ctx, method, url, body)
+ if err != nil {
+ cancel()
+ return nil, nil, err
+ }
+
+ return req, cancel, nil
+}
diff --git a/internal/httpclient/client_test.go b/internal/httpclient/client_test.go
new file mode 100644
index 0000000..a8b2216
--- /dev/null
+++ b/internal/httpclient/client_test.go
@@ -0,0 +1,44 @@
+package httpclient
+
+import (
+ "net/http"
+ "testing"
+ "time"
+)
+
+func TestNewRequest_SetsDeadline(t *testing.T) {
+ req, cancel, err := NewRequest(http.MethodGet, "https://example.com", nil)
+ if err != nil {
+ t.Fatalf("NewRequest returned error: %v", err)
+ }
+ defer cancel()
+
+ deadline, ok := req.Context().Deadline()
+ if !ok {
+ t.Fatal("expected request context to include a deadline")
+ }
+
+ remaining := time.Until(deadline)
+ if remaining <= 0 {
+ t.Fatalf("expected future deadline, got %v", remaining)
+ }
+ if remaining > DefaultTimeout+time.Second {
+ t.Fatalf("expected deadline near %v, got %v", DefaultTimeout, remaining)
+ }
+}
+
+func TestNewRequest_InvalidMethod(t *testing.T) {
+ req, cancel, err := NewRequest("bad method", "https://example.com", nil)
+ if cancel != nil {
+ defer cancel()
+ }
+ if err == nil {
+ t.Fatalf("expected invalid method error, got request %#v", req)
+ }
+}
+
+func TestDo_UsesSharedTimeout(t *testing.T) {
+ if defaultClient.Timeout != DefaultTimeout {
+ t.Fatalf("expected shared timeout %v, got %v", DefaultTimeout, defaultClient.Timeout)
+ }
+}