HTTP client
The httpclient module provides standard *http.Clients, instrumented with OpenTelemetry: every request gets a client span, metrics and a log, and carries the trace context to the server it calls.
go get github.com/go-oryn/oryn/httpclientQuick start
type WeatherClient struct { client *http.Client}
func NewWeatherClient(client *http.Client) *WeatherClient { return &WeatherClient{client: client}}
func (c *WeatherClient) Temperature(ctx context.Context, city string) (*Temperature, error) { request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://weather.example.com/cities/"+url.PathEscape(city), nil) if err != nil { return nil, err }
response, err := c.client.Do(request) ...}core.New( httpclient.Module, fx.Provide(NewWeatherClient),)httpclient.Moduleprovides the default*http.Client, and the*httpclient.Factoryto build more clients.- Build the requests with the context of the caller,
http.NewRequestWithContext: the client span is then a child of the current span, and the request is canceled with it. - Every request is traced, measured and logged, see Observability.
- The client has a 30 seconds timeout by default, see Configuration.
Factory
The *httpclient.Factory builds clients with their own settings. They share the registered middlewares and the instrumentation:
func NewWeatherClient(factory *httpclient.Factory) *WeatherClient { return &WeatherClient{client: factory.NewClient( httpclient.WithTimeout(5*time.Second), httpclient.WithMiddlewares(NewAPIKeyMiddleware("weather")), )}}| Option | Description |
|---|---|
WithTimeout(d) |
The client timeout, instead of httpclient.timeout. 0 means none. |
WithMiddlewares(m...) |
Middlewares of this client only, applied after the registered ones. |
WithTransport(rt) |
The base transport, instead of the configured one: a custom TLS configuration, a proxy… The middlewares and the instrumentation still wrap it. |
Middlewares
Middlewares run code around every request: authentication headers, retries, rate limiting… A middleware implements httpclient.Middleware, or is a func(http.RoundTripper) http.RoundTripper. httpclient.RoundTripperFunc keeps them short:
func NewAuthMiddleware(tokens *TokenSource) httpclient.Middleware { return httpclient.MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper { return httpclient.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { token, err := tokens.Token(r.Context()) if err != nil { return nil, err }
// A transport must not modify the request it receives: clone it. r = r.Clone(r.Context()) r.Header.Set("Authorization", "Bearer "+token)
return next.RoundTrip(r) }) })}Register them with AsMiddleware, as values or constructors whose parameters are injected by fx:
httpclient.AsMiddleware(NewAuthMiddleware)Registered middlewares apply to every client of the factory, in registration order: the first registered middleware is the outermost one. A request goes through, from the outermost:
- the registered middlewares, then the
WithMiddlewaresones, - the OpenTelemetry instrumentation,
- the request log,
- the base transport.
As the middlewares wrap the instrumentation, a middleware retrying a request produces one client span per attempt.
Configuration
The httpclient.* keys set the timeout, the connection pool, the request logs and the URLs excluded from telemetry. See Configuration.
