The package provides a very thin wrapper (no external dependencies) for http.Client, allowing the use of layers (middlewares) at the http.RoundTripper level. The goal is to maintain the way users leverage the stdlib HTTP client while adding a few useful extras on top of the standard http.Client.
Please note: this is not a replacement for http.Client, but rather a companion library.
rq := requester.New( // make the requester
http.Client{Timeout: 5*time.Second}, // set http client
requester.MaxConcurrent(8), // maximum number of concurrent requests
requester.JSON, // set json headers
requester.Header("X-AUTH", "123456789"),// set some auth header
requester.Logger(requester.StdLogger), // enable logging to stdout
)
req := http.NewRequest("GET", "http://example.com/api", nil) // create the usual http.Request
req.Header.Set("foo", "bar") // do the usual things with request, for example set some custome headers
resp, err := rq.Do(req) // instead of client.Do call requester.Dogo get -u github.com/go-pkgz/requester
Built-in middlewares:
Header- appends user-defined headers to all requests.MaxConcurrent- sets maximum concurrencyRetry- sets retry on errors and status codesJSON- sets headers"Content-Type": "application/json"and"Accept": "application/json"BasicAuth(user, passwd string)- adds HTTP Basic Authentication
Interfaces for external middlewares:
Repeater- sets repeater to retry failed requests. Doesn't provide repeater implementation but wraps it. Compatible with any repeater (for example go-pkgz/repeater) implementing a single method interfaceDo(ctx context.Context, fun func() error, errors ...error) (err error)interface.Cache- sets anyLoadingCacheimplementation to be used for request/response caching. Doesn't provide cache, but wraps it. Compatible with any cache (for example a family of caches from go-pkgz/lcw) implementing a single-method interfaceGet(key string, fn func() (interface{}, error)) (val interface{}, err error)Logger- sets logger, compatible with any implementation of a single-method interfaceLogf(format string, args ...interface{}), for example go-pkgz/lgrCircuitBreaker- sets circuit breaker, interface compatible with sony/gobreaker
Users can add any custom middleware. All it needs is a handler RoundTripperHandler func(http.RoundTripper) http.RoundTripper.
Convenient functional adapter middleware.RoundTripperFunc provided.
See examples of the usage in _example
Header middleware adds user-defined headers to all requests. It expects a map of headers to be added. For example:
rq := requester.New(http.Client{}, middleware.Header("X-Auth", "123456789"))MaxConcurrent middleware can be used to limit the concurrency of a given requester and limit overall concurrency for multiple requesters. For the first case, MaxConcurrent(N) should be created in the requester chain of middlewares. For example, rq := requester.New(http.Client{Timeout: 3 * time.Second}, middleware.MaxConcurrent(8)). To make it global, MaxConcurrent should be created once, outside the chain, and passed into each requester. For example:
mc := middleware.MaxConcurrent(16)
rq1 := requester.New(http.Client{Timeout: 3 * time.Second}, mc)
rq2 := requester.New(http.Client{Timeout: 1 * time.Second}, middleware.JSON, mc)Retry middleware provides a flexible retry mechanism with different backoff strategies. By default, it retries on network errors and 5xx responses.
// retry 3 times with exponential backoff, starting from 100ms
rq := requester.New(http.Client{}, middleware.Retry(3, 100*time.Millisecond))
// retry with custom options
rq := requester.New(http.Client{}, middleware.Retry(3, 100*time.Millisecond,
middleware.RetryWithBackoff(middleware.BackoffLinear), // use linear backoff
middleware.RetryMaxDelay(5*time.Second), // cap maximum delay
middleware.RetryWithJitter(0.1), // add 10% randomization
middleware.RetryOnCodes(503, 502), // retry only on specific codes
// or middleware.RetryExcludeCodes(404, 401), // alternatively, retry on all except these codes
middleware.RetryBufferBodies(true), // enable body buffering for retries
middleware.RetryMaxBufferSize(50*1024*1024), // set max buffer size to 50MB
))Default configuration:
- 3 attempts
- Initial delay: 100ms
- Max delay: 30s
- Exponential backoff
- 10% jitter
- Retries on 5xx status codes
- Body buffering: disabled (preserves streaming behavior)
For requests with bodies (POST, PUT, PATCH), the retry middleware handles body replay as follows:
- If
req.GetBodyis set (automatic forstrings.Reader,bytes.Buffer,bytes.Reader), it uses that for retries - If
req.GetBodyis nil and body buffering is disabled (default), requests won't be retried to preserve streaming - If body buffering is enabled with
RetryBufferBodies(true):- Bodies up to
maxBufferSizeare buffered and can be retried - Bodies exceeding
maxBufferSizefail immediately with an error
- Bodies up to
- Use
RetryMaxBufferSize(size)to adjust the buffer limit (default: 10MB when enabled)
This default behavior ensures large file uploads work without memory issues, while you can opt-in to buffering for API requests that need retries.
Retry Options:
RetryWithBackoff(t BackoffType)- set backoff strategy (Constant, Linear, or Exponential)RetryMaxDelay(d time.Duration)- cap the maximum delay between retriesRetryWithJitter(f float64)- add randomization to delays (0-1.0 factor)RetryOnCodes(codes ...int)- retry only on specific status codesRetryExcludeCodes(codes ...int)- retry on all codes except specifiedRetryBufferBodies(enabled bool)- enable or disable automatic body buffering for retriesRetryMaxBufferSize(size int64)- set maximum size of request bodies that will be buffered
Note: RetryOnCodes and RetryExcludeCodes are mutually exclusive and can't be used together.
Cache middleware provides an in-memory caching layer for HTTP responses. It improves performance by avoiding repeated network calls for the same request.
rq := requester.New(http.Client{}, middleware.Cache())By default:
- Only GET requests are cached
- TTL (Time-To-Live) is 5 minutes
- Maximum cache size is 1000 entries
- Caches only HTTP 200 responses
rq := requester.New(http.Client{}, middleware.Cache(
middleware.CacheTTL(10*time.Minute), // change TTL to 10 minutes
middleware.CacheSize(500), // limit cache to 500 entries
middleware.CacheMethods(http.MethodGet, http.MethodPost), // allow caching for GET and POST
middleware.CacheStatuses(200, 201, 204), // cache only responses with these status codes
middleware.CacheWithBody, // include request body in cache key
middleware.CacheWithHeaders("Authorization", "X-Custom-Header"), // include selected headers in cache key
))By default, the cache key is generated using:
- HTTP method
- Full URL
- (Optional) Headers (if
CacheWithHeadersis enabled) - (Optional) Body (if
CacheWithBodyis enabled)
For example, enabling CacheWithHeaders("Authorization") will cache the same URL differently for each unique Authorization token.
- Entries expire when the TTL is reached.
- If the cache reaches its maximum size, the oldest entry is evicted (FIFO order).
- Only caches complete HTTP responses. Streaming responses are not supported.
- Does not cache responses with status codes other than 200 (unless explicitly allowed).
- Uses in-memory storage, meaning the cache resets on application restart.
JSON middleware sets headers "Content-Type": "application/json" and "Accept": "application/json".
rq := requester.New(http.Client{}, middleware.JSON)BasicAuth middleware adds HTTP Basic Authentication to all requests. It expects a username and password. For example:
rq := requester.New(http.Client{}, middleware.BasicAuth("user", "passwd"))Logger should implement Logger interface with a single method Logf(format string, args ...interface{}).
For convenience, func type LoggerFunc is provided as an adapter to allow the use of ordinary functions as Logger.
Two basic implementations included:
NoOpLoggerdo-nothing logger (default)StdLoggerwrapper for stdlib logger.
logging options:
Prefix(prefix string)sets prefix for each logged lineWithBody- allows request's body loggingWithHeaders- allows request's headers logging
Note: If logging is allowed, it will log the URL, method, and may log headers and the request body. It may affect application security. For example, if a request passes some sensitive information as part of the body or header. In this case, consider turning logging off or providing your own logger to suppress all that you need to hide.
If the request is limited, it will wait till the limit is released.
Cache expects the LoadingCache interface to implement a single method: Get(key string, fn func() (interface{}, error)) (val interface{}, err error). LCW can be used directly, and in order to adopt other caches, see the provided LoadingCacheFunc.
By default, only GET calls are cached. This can be changed with the Methods(methods ...string) option. The default key is composed of the full URL.
Several options define what part of the request will be used for the key:
KeyWithHeaders- adds all headers to a keyKeyWithHeadersIncluded(headers ...string)- adds only requested headersKeyWithHeadersExcluded(headers ...string)- adds all headers excludedKeyWithBody- adds the request's body, limited to the first 16k of the bodyKeyFunc- any custom logic provided by the caller
example: cache.New(lruCache, cache.Methods("GET", "POST"), cache.KeyFunc() {func(r *http.Request) string {return r.Host})
Cache is not compatible with HTTP streaming mode. Practically, this is rare and exotic, but allowing Cache will effectively transform the streaming response into a "get it all" typical response. This is due to the fact that the cache has to read the response body fully to save it, so technically streaming will be working, but the client will receive all the data at once.
Repeater expects a single method interface Do(fn func() error, failOnCodes ...error) (err error). repeater can be used directly.
By default, the repeater will retry on any error and any status code >= 400. However, the user can pass failOnCodes to explicitly define which status codes should be treated as errors and retry only on those. For example: Repeater(repeaterSvc, 500, 400) repeats requests on 500 and 400 statuses only.
In a special case where the user wants to retry only on the underlying transport errors (network, timeouts, etc.) and not on any status codes Repeater(repeaterSvc, 0) can be used.
Users can add any additional handlers (middleware) to the chain. Each middleware provides middleware.RoundTripperHandler and
can alter the request or implement any other custom functionality.
Example of a handler resetting a particular header:
maskHeader := func(http.RoundTripper) http.RoundTripper {
fn := func(req *http.Request) (*http.Response, error) {
req.Header.Del("deleteme")
return next(req)
}
return middleware.RoundTripperFunc(fn)
}
rq := requester.New(http.Client{}, maskHeader)There are 3 ways to add middleware(s):
- Pass it to the
Newconstructor, i.e.requester.New(http.Client{}, middleware.MaxConcurrent(8), middleware.Header("foo", "bar")) - Add after construction with the
Usemethod - Create a new, inherited requester by using
With:
rq := requester.New(http.Client{}, middleware.Header("foo", "bar")) // make requester enforcing header foo:bar
resp, err := rq.Do(some_http_req) // send a request
rqLimited := rq.With(middleware.MaxConcurrent(8)) // make requester from rq (foo:bar enforced) and add 8 max concurrency
resp, err := rqLimited.Do(some_http_req)For convenience, requester.Client() returns *http.Client with all middlewares injected. From this point, the user can call Do on this client, and it will invoke the request with all the middlewares.
CircuitBreakerFunc func(req func() (interface{}, error)) (interface{}, error)- adapter to allow the use of an ordinary functions as CircuitBreakerSvc.logger.Func func(format string, args ...interface{})- functional adapter forlogger.Service.cache.ServiceFunc func(key string, fn func() (interface{}, error)) (interface{}, error)- functional adapter forcache.Service.RoundTripperFunc func(*http.Request) (*http.Response, error)- functional adapter for RoundTripperHandler