This is a tiny Go retryable http client to use when consuming Rest APIs.
It uses go-retryablehttp underneath.
For more information see the godoc.
WithHttpClient
adds a specified httpClient to be usedWithTimeout
adds a timeout to the clientWithMaxIdleConns
defines the maximum number of idle (keep-alive) connections across all hosts.WithMaxIdleConnsPerHost
defines the maximum idle (keep-alive) connections to keep per-host.WithMaxConnsPerHost
limits the total number of connections per hostWithMaxRetries
limits the maximum number of retriesWithRetryWaitMin
specifies minimum time to wait before retryingWithRetryWaitMax
specifies maximum time to wait before retryingWithCheckRetryPolicy
specifies the policy for handling retries, and is called after each requestWithRequestDumpLogger
specifies a function that receives the request dump for logging purposesWithResponseDumpLogger
specifies a function that receives the response dump for logging purposes
DoNotRetry
policy does not retry a failed request, default policy if none is specifiedEof
policy retries a request in case of EOF error
const url = "http://someurl/"
client := httpclient.New(
httpclient.WithTimeout(30 * time.Second),
httpclient.WithMaxConnsPerHost(200),
httpclient.WithMaxIdleConnsPerHost(200),
httpclient.WithMaxIdleConns(200),
httpclient.WithMaxRetries(5),
httpclient.WithRetryWaitMin(1 * time.Second),
httpclient.WithRetryWaitMax(5 * time.Second),
// you can pick one from `policies` package
// httpclient.WithCheckRetryPolicy(policies.Eof),
// or provide a custom one
// httpclient.WithCheckRetryPolicy(func(ctx context.Context, resp *http.Response, err error) (bool, error) {
// if resp != nil {
// statusCode := resp.StatusCode
// if statusCode == http.StatusBadRequest {
// return true, err
// }
// }
// return false, err
//}),
)
You can provide your own http client as well:
client := httpclient.New(
httpclient.WithHttpClient(&http.Client{Timeout: time.Duration(25 * time.Second)}),
httpclient.WithMaxConnsPerHost(200),
httpclient.WithMaxIdleConnsPerHost(200),
httpclient.WithMaxIdleConns(200),
httpclient.WithMaxRetries(5),
)
Without headers:
ctx := context.Background()
req, err := httpclient.NewRequest(ctx, http.MethodGet, url)
if err != nil {
// ...
}
With headers:
ctx := context.Background()
req, err := httpclient.NewRequestWithHeaders(ctx,
http.MethodGet,
url,
map[string]string{"Custom-Header": "some value"},
)
if err != nil {
// ...
}
Without headers, passing a JSON string:
ctx := context.Background()
req, err := httpclient.NewJsonRequest(ctx,
http.MethodPost,
url,
`{"user":"tiago", "email":"tiago@email.com"}`,
)
if err != nil {
// ...
}
Without headers, passing a struct:
type Payload struct {
User string `json:"user"`
Email string `json:"email"`
}
...
ctx := context.Background()
req, err := httpclient.NewJsonRequest(ctx,
http.MethodPost,
url,
Payload{User: "user", Email: "user@email.com"},
)
if err != nil {
// ...
}
With headers, passing a JSON string:
ctx := context.Background()
req, err := httpclient.NewJsonRequestWithHeaders(ctx,
http.MethodPost,
url,
`{"user":"tiago", "email":"tiago@email.com"}`,
map[string]string{"Custom-Header": "some value"},
)
if err != nil {
// ...
}
With headers, passing a struct:
type Payload struct {
User string `json:"user"`
Email string `json:"email"`
}
...
ctx := context.Background()
req, err := httpclient.NewJsonRequestWithHeaders(ctx,
http.MethodPost,
url,
Payload{User: "user", Email: "user@email.com"},
map[string]string{"Custom-Header": "some value"},
)
if err != nil {
// ...
}
Not unmarshalling the response:
resp, err := client.SendRequest(req)
if err != nil {
// ...
}
Unmarshalling the response to a given struct:
type Response struct {
Message string `json:"message"`
}
...
var someResponse Response
resp, err := client.SendRequestAndUnmarshallJsonResponse(req, &someResponse)
if err != nil {
// ...
}
// do something with http resp (`resp`)
func logRequestDump(dump []byte) {
fmt.Print("request sent:\n\n")
fmt.Println(string(dump))
}
client := httpclient.New(httpclient.WithRequestDumpLogger(logRequestDump, false))
Sample output:
request sent:
GET /get HTTP/1.1
Host: localhost
User-Agent: Go-http-client/1.1
Custom-Header-1: some value
Custom-Header-2: some other value
Accept-Encoding: gzip
func logRequestDump(dump []byte) {
fmt.Print("request sent:\n\n")
fmt.Println(string(dump))
}
client := httpclient.New(httpclient.WithRequestDumpLogger(logRequestDump, true))
Sample output:
request sent:
POST /post HTTP/1.1
Host: localhost
User-Agent: Go-http-client/1.1
Content-Length: 55
Content-Type: application/json
Custom-Header-1: some value
Custom-Header-2: some other value
Accept-Encoding: gzip
{"name":"Steve Harris","email":"steve@ironmaiden.com"}
func logResponseDump(dump []byte) {
fmt.Print("received response:\n\n")
fmt.Println(string(dump))
}
client := httpclient.New(httpclient.WithResponseDumpLogger(logResponseDump, false))
Sample output:
received response:
HTTP/1.1 200 OK
Content-Length: 542
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Type: application/json
Date: Fri, 07 Apr 2023 00:10:19 GMT
Server: gunicorn/19.9.0
func logResponseDump(dump []byte) {
fmt.Print("received response:\n\n")
fmt.Println(string(dump))
}
client := httpclient.New(httpclient.WithResponseDumpLogger(logResponseDump, true))
Sample output:
received response:
HTTP/1.1 200 OK
Content-Length: 542
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Type: application/json
Date: Fri, 07 Apr 2023 00:11:20 GMT
Server: gunicorn/19.9.0
{
"args": {},
"data": "{\"name\":\"Steve Harris\",\"email\":\"steve@ironmaiden.com\"}\n",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "55",
"Content-Type": "application/json",
"Custom-Header-1": "some value",
"Custom-Header-2": "some other value",
"Host": "localhost",
"User-Agent": "Go-http-client/1.1"
},
"json": {
"email": "steve@ironmaiden.com",
"name": "Steve Harris"
},
"origin": "240.10.0.1",
"url": "http://localhost/post"
}
make test
make coverage
make int-tests
It launches (via Docker) an instance of httpbin.
make vul-check