diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-11 18:28:41 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-11 18:28:41 +0200 |
| commit | 9706e53620045c20bf732054a286183c70f77581 (patch) | |
| tree | 166e822e9d41b0cc980d4481c5c175ec7164a107 /internal/httpclient | |
| parent | 1c12325c64bb734dd0ae95a0c803de1ff45d2b4c (diff) | |
fix(api): add timed shared HTTP client
Diffstat (limited to 'internal/httpclient')
| -rw-r--r-- | internal/httpclient/client.go | 30 | ||||
| -rw-r--r-- | internal/httpclient/client_test.go | 44 |
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) + } +} |
