HTTP Client
> Instrumented outbound HTTP client with APM events and trace propagation.
The httpclient package is a thin wrapper around net/http that
dispatches APM events on every outbound request. Latency, status, and
body sizes flow into your observability stack automatically.
Import path: github.com/velocitykode/velocity/httpclient
Creating a client
c := httpclient.New()Options:
c := httpclient.New(
httpclient.WithBaseURL("https://api.example.com"),
httpclient.WithTimeout(10 * time.Second),
httpclient.WithHTTPClient(&http.Client{ /* custom transport */ }),
)Default timeout is 30 seconds. The default transport rejects chains with more than 10 redirects.
Methods
All methods take a context.Context as the first argument so that
cancellation, deadlines, and trace IDs propagate through the call:
ctx := r.Context()
resp, err := c.Get(ctx, "/users/42")
if err != nil {
return err
}
defer resp.Body.Close()
// POST with JSON
body := strings.NewReader(`{"name":"alice"}`)
resp, err := c.Post(ctx, "/users", "application/json", body)
// PUT, PATCH, DELETE also available
resp, err := c.Put(ctx, "/users/42", "application/json", body)
resp, err := c.Patch(ctx, "/users/42", "application/json", body)
resp, err := c.Delete(ctx, "/users/42")For full control, build an *http.Request yourself and call Do:
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/search", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.URL.RawQuery = "q=velocity"
resp, err := c.Do(ctx, req)Base URL resolution
When WithBaseURL(...) is set, relative paths starting with / are
resolved against the base URL. Absolute URLs are used as-is:
c := httpclient.New(httpclient.WithBaseURL("https://api.example.com"))
c.Get(ctx, "/users") // → https://api.example.com/users
c.Get(ctx, "https://other.example/x") // → https://other.example/xEvents
Every call dispatches one of two events through the application’s event dispatcher:
*httpclient.RequestSent— fired on every successful response*httpclient.RequestFailed— fired on transport errors
type RequestSent struct {
Context context.Context
Method string
URL string
StatusCode int
DurationMs int64
RequestSize int64
ResponseSize int64
TraceID string
SpanID string
ParentID string
}
type RequestFailed struct {
Context context.Context
Method string
URL string
Error string
DurationMs int64
TraceID string
SpanID string
ParentID string
}Trace IDs are read from the context via the trace package — if your
request came in through Velocity’s middleware, the outgoing call
automatically carries the incoming trace.
Wiring a dispatcher
c := httpclient.New()
c.SetEventDispatcher(func(event interface{}) error {
return v.Events.Dispatch(event)
})Once wired, APM agents subscribing to http.request.sent and
http.request.failed events will record every outbound request.
Design notes
- One call = one event. No sampling inside the client. Filter in listeners if volume is a concern.
- No retries.
httpclientdoesn’t retry failed requests; compose a retry loop in your caller when you need it, or plug in a custom*http.ClientviaWithHTTPClient. - No middleware chain. Wrap the client if you need cross-cutting behavior (auth headers, circuit breakers) rather than extending it.