Files
httpx/client_options.go
Aleksey Shakhmatov e8c4577c6f Return ErrResponseTooLarge instead of truncating response body
WithMaxResponseBody wrapped the body in io.LimitedReader, which returns EOF
at the cap, so Bytes/JSON/XML silently returned a truncated body with a nil
error despite the documented contract. Read one byte past the limit and
return the new ErrResponseTooLarge sentinel when exceeded; bodies exactly at
the limit still succeed.
2026-05-23 13:47:13 +03:00

97 lines
2.9 KiB
Go

package httpx
import (
"log/slog"
"net/http"
"time"
"git.codelab.vc/pkg/httpx/balancer"
"git.codelab.vc/pkg/httpx/circuitbreaker"
"git.codelab.vc/pkg/httpx/middleware"
"git.codelab.vc/pkg/httpx/retry"
)
type clientOptions struct {
baseURL string
timeout time.Duration
transport http.RoundTripper
logger *slog.Logger
errorMapper ErrorMapper
middlewares []middleware.Middleware
retryOpts []retry.Option
enableRetry bool
cbOpts []circuitbreaker.Option
enableCB bool
endpoints []balancer.Endpoint
balancerOpts []balancer.Option
maxResponseBody int64
}
// Option configures a Client.
type Option func(*clientOptions)
// WithBaseURL sets the base URL prepended to all relative request paths.
func WithBaseURL(url string) Option {
return func(o *clientOptions) { o.baseURL = url }
}
// WithTimeout sets the overall request timeout.
func WithTimeout(d time.Duration) Option {
return func(o *clientOptions) { o.timeout = d }
}
// WithTransport sets the base http.RoundTripper. Defaults to http.DefaultTransport.
func WithTransport(rt http.RoundTripper) Option {
return func(o *clientOptions) { o.transport = rt }
}
// WithLogger enables structured logging of requests and responses.
func WithLogger(l *slog.Logger) Option {
return func(o *clientOptions) { o.logger = l }
}
// WithErrorMapper sets a function that maps HTTP responses to errors.
func WithErrorMapper(m ErrorMapper) Option {
return func(o *clientOptions) { o.errorMapper = m }
}
// WithMiddleware appends user middlewares to the chain.
// These run between logging and retry in the middleware stack.
func WithMiddleware(mws ...middleware.Middleware) Option {
return func(o *clientOptions) { o.middlewares = append(o.middlewares, mws...) }
}
// WithRetry enables retry with the given options.
func WithRetry(opts ...retry.Option) Option {
return func(o *clientOptions) {
o.enableRetry = true
o.retryOpts = opts
}
}
// WithCircuitBreaker enables per-host circuit breaking.
func WithCircuitBreaker(opts ...circuitbreaker.Option) Option {
return func(o *clientOptions) {
o.enableCB = true
o.cbOpts = opts
}
}
// WithEndpoints sets the endpoints for load balancing.
func WithEndpoints(eps ...balancer.Endpoint) Option {
return func(o *clientOptions) { o.endpoints = eps }
}
// WithBalancer configures the load balancer strategy and options.
func WithBalancer(opts ...balancer.Option) Option {
return func(o *clientOptions) { o.balancerOpts = opts }
}
// WithMaxResponseBody limits the number of bytes read from response bodies
// by Response.Bytes (and by extension String, JSON, XML). If the response
// body exceeds n bytes, reading returns ErrResponseTooLarge instead of
// silently truncating. A value of 0 means no limit (the default).
func WithMaxResponseBody(n int64) Option {
return func(o *clientOptions) { o.maxResponseBody = n }
}