update dependencies

This commit is contained in:
Cory Bennett
2017-09-06 11:35:00 -07:00
parent 9453179251
commit aa876cd588
308 changed files with 46154 additions and 33733 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) [2016] [Seth Ammons]
Copyright (c) SendGrid 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -35,7 +35,7 @@ client.Backoff = func(retry int) time.Duration {
For a complete and working example, see the sample directory.
`pester` allows you to use a constructor to control:
- backoff strategy
- reties
- retries
- concurrency
- keeping a log for debugging
```go
+51 -29
View File
@@ -1,7 +1,6 @@
package pester
// pester provides additional resiliency over the standard http client methods by
// Package pester provides additional resiliency over the standard http client methods by
// allowing you to control concurrency, retries, and a backoff strategy.
package pester
import (
"bytes"
@@ -9,7 +8,6 @@ import (
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"net/http"
"net/url"
@@ -17,6 +15,15 @@ import (
"time"
)
//ErrUnexpectedMethod occurs when an http.Client method is unable to be mapped from a calling method in the pester client
var ErrUnexpectedMethod = errors.New("unexpected client method, must be one of Do, Get, Head, Post, or PostFrom")
// ErrReadingBody happens when we cannot read the body bytes
var ErrReadingBody = errors.New("error reading body")
// ErrReadingRequestBody happens when we cannot read the request body bytes
var ErrReadingRequestBody = errors.New("error reading request body")
// Client wraps the http client and exposes all the functionality of the http.Client.
// Additionally, Client provides pester specific values for handling resiliency.
type Client struct {
@@ -33,6 +40,7 @@ type Client struct {
MaxRetries int
Backoff BackoffStrategy
KeepLog bool
LogHook LogHook
SuccessReqNum int
SuccessRetryNum int
@@ -76,6 +84,12 @@ type params struct {
data url.Values
}
var random *rand.Rand
func init() {
random = rand.New(rand.NewSource(time.Now().UnixNano()))
}
// New constructs a new DefaultClient with sensible default values
func New() *Client {
return &Client{
@@ -95,6 +109,10 @@ func NewExtendedClient(hc *http.Client) *Client {
return c
}
// LogHook is used to log attempts as they happen. This function is never called,
// however, if KeepLog is set to true.
type LogHook func(e ErrEntry)
// BackoffStrategy is used to determine how long a retry request should wait until attempted
type BackoffStrategy func(retry int) time.Duration
@@ -108,13 +126,13 @@ func DefaultBackoff(_ int) time.Duration {
// ExponentialBackoff returns ever increasing backoffs by a power of 2
func ExponentialBackoff(i int) time.Duration {
return time.Duration(math.Pow(2, float64(i))) * time.Second
return time.Duration(1<<uint(i)) * time.Second
}
// ExponentialJitterBackoff returns ever increasing backoffs by a power of 2
// with +/- 0-33% to prevent sychronized reuqests.
func ExponentialJitterBackoff(i int) time.Duration {
return jitter(int(math.Pow(2, float64(i))))
return jitter(int(1 << uint(i)))
}
// LinearBackoff returns increasing durations, each a second longer than the last
@@ -134,14 +152,8 @@ func jitter(i int) time.Duration {
maxJitter := ms / 3
rand.Seed(time.Now().Unix())
jitter := rand.Intn(maxJitter + 1)
if rand.Intn(2) == 1 {
ms = ms + jitter
} else {
ms = ms - jitter
}
// ms ± rand
ms += random.Intn(2*maxJitter) - maxJitter
// a jitter of 0 messes up the time.Tick chan
if ms <= 0 {
@@ -206,14 +218,14 @@ func (c *Client) pester(p params) (*http.Response, error) {
if p.req != nil && p.req.Body != nil {
originalRequestBody, err = ioutil.ReadAll(p.req.Body)
if err != nil {
return &http.Response{}, errors.New("error reading request body")
return nil, ErrReadingRequestBody
}
p.req.Body.Close()
}
if p.body != nil {
originalBody, err = ioutil.ReadAll(p.body)
if err != nil {
return &http.Response{}, errors.New("error reading body")
return nil, ErrReadingBody
}
}
@@ -238,7 +250,6 @@ func (c *Client) pester(p params) (*http.Response, error) {
return
default:
}
resp := &http.Response{}
// rehydrate the body (it is drained each read)
if len(originalRequestBody) > 0 {
@@ -248,6 +259,7 @@ func (c *Client) pester(p params) (*http.Response, error) {
p.body = bytes.NewBuffer(originalBody)
}
var resp *http.Response
// route the calls
switch p.method {
case "Do":
@@ -260,6 +272,8 @@ func (c *Client) pester(p params) (*http.Response, error) {
resp, err = httpClient.Post(p.url, p.bodyType, p.body)
case "PostForm":
resp, err = httpClient.PostForm(p.url, p.data)
default:
err = ErrUnexpectedMethod
}
// Early return if we have a valid result
@@ -292,7 +306,7 @@ func (c *Client) pester(p params) (*http.Response, error) {
}
// prevent a 0 from causing the tick to block, pass additional microsecond
<-time.Tick(c.Backoff(i) + 1*time.Microsecond)
<-time.After(c.Backoff(i) + 1*time.Microsecond)
}
}(req, p)
}
@@ -320,14 +334,13 @@ func (c *Client) pester(p params) (*http.Response, error) {
}
}()
select {
case res := <-resultCh:
c.Lock()
defer c.Unlock()
c.SuccessReqNum = res.req
c.SuccessRetryNum = res.retry
return res.resp, res.err
}
res := <-resultCh
c.Lock()
defer c.Unlock()
c.SuccessReqNum = res.req
c.SuccessRetryNum = res.retry
return res.resp, res.err
}
// LogString provides a string representation of the errors the client has seen
@@ -336,12 +349,17 @@ func (c *Client) LogString() string {
defer c.Unlock()
var res string
for _, e := range c.ErrLog {
res += fmt.Sprintf("%d %s [%s] %s request-%d retry-%d error: %s\n",
e.Time.Unix(), e.Method, e.Verb, e.URL, e.Request, e.Retry, e.Err)
res += c.FormatError(e)
}
return res
}
// Format the Error to human readable string
func (c *Client) FormatError(e ErrEntry) string {
return fmt.Sprintf("%d %s [%s] %s request-%d retry-%d error: %s\n",
e.Time.Unix(), e.Method, e.Verb, e.URL, e.Request, e.Retry, e.Err)
}
// LogErrCount is a helper method used primarily for test validation
func (c *Client) LogErrCount() int {
c.Lock()
@@ -358,8 +376,12 @@ func (c *Client) EmbedHTTPClient(hc *http.Client) {
func (c *Client) log(e ErrEntry) {
if c.KeepLog {
c.Lock()
defer c.Unlock()
c.ErrLog = append(c.ErrLog, e)
c.Unlock()
} else if c.LogHook != nil {
// NOTE: There is a possibility that Log Printing hook slows it down.
// but the consumer can always do the Job in a go-routine.
c.LogHook(e)
}
}
+111 -19
View File
@@ -1,4 +1,4 @@
package pester_test
package pester
import (
"fmt"
@@ -13,14 +13,13 @@ import (
"net/http"
"net/http/cookiejar"
"github.com/sethgrid/pester"
"errors"
)
func TestConcurrentRequests(t *testing.T) {
t.Parallel()
c := pester.New()
c := New()
c.Concurrency = 3
c.KeepLog = true
@@ -43,7 +42,7 @@ func TestConcurrentRequests(t *testing.T) {
func TestConcurrent2Retry0(t *testing.T) {
t.Parallel()
c := pester.New()
c := New()
c.Concurrency = 2
c.MaxRetries = 0
c.KeepLog = true
@@ -67,7 +66,7 @@ func TestConcurrent2Retry0(t *testing.T) {
func TestDefaultBackoff(t *testing.T) {
t.Parallel()
c := pester.New()
c := New()
c.KeepLog = true
nonExistantURL := "http://localhost:9000/foo"
@@ -102,10 +101,90 @@ func TestDefaultBackoff(t *testing.T) {
}
func TestFormatError(t *testing.T) {
t.Parallel()
err := errors.New("Get http://localhost:9000/foo: dial tcp 127.0.0.1:9000: getsockopt: connection refused")
expected := "1491271979 Get [GET] http://localhost:9000/foo request-0 retry-2 error: "+ err.Error()+"\n"
e := ErrEntry{
Time: time.Unix(1491271979, 0),
Method: "Get",
URL: "http://localhost:9000/foo",
Verb: http.MethodGet,
Request: 0,
Retry: 2,
Attempt: 1,
Err: err,
}
c := New()
formatted := c.FormatError(e)
if strings.Compare(expected, formatted) != 0 {
t.Errorf("\nExpected:\n%s\nGot:\n%s", expected, formatted)
}
}
func TestCustomLogHook(t *testing.T) {
t.Parallel()
expectedRetries := 5
errorLines := []ErrEntry{}
c := New()
//c.KeepLog = true
c.MaxRetries = expectedRetries
c.Backoff = func(_ int) time.Duration {
return 10 * time.Microsecond
}
c.LogHook = func(e ErrEntry) {
errorLines = append(errorLines, e)
}
nonExistantURL := "http://localhost:9000/foo"
_, err := c.Get(nonExistantURL)
if err == nil {
t.Fatal("expected to get an error")
}
c.Wait()
// in the event of an error, let's see what the logs were
if expectedRetries != len(errorLines) {
t.Errorf("Expected %d lines to be emitted. Got %d", expectedRetries, errorLines)
}
}
func TestDefaultLogHook(t *testing.T) {
t.Parallel()
errorLines := 0
c := New()
//c.KeepLog = true
c.MaxRetries = 5
c.Backoff = func(_ int) time.Duration {
return 10 * time.Microsecond
}
nonExistantURL := "http://localhost:9000/foo"
_, err := c.Get(nonExistantURL)
if err == nil {
t.Fatal("expected to get an error")
}
c.Wait()
// in the event of an error, let's see what the logs were
if errorLines != 0 {
t.Errorf("Expected 0 lines to be emitted. Got %d", errorLines)
}
}
func TestLinearJitterBackoff(t *testing.T) {
t.Parallel()
c := pester.New()
c.Backoff = pester.LinearJitterBackoff
c := New()
c.Backoff = LinearJitterBackoff
c.KeepLog = true
nonExistantURL := "http://localhost:9000/foo"
@@ -142,9 +221,9 @@ func TestLinearJitterBackoff(t *testing.T) {
func TestExponentialBackoff(t *testing.T) {
t.Parallel()
c := pester.New()
c := New()
c.MaxRetries = 4
c.Backoff = pester.ExponentialBackoff
c.Backoff = ExponentialBackoff
c.KeepLog = true
nonExistantURL := "http://localhost:9000/foo"
@@ -193,7 +272,7 @@ func TestCookiesJarPersistence(t *testing.T) {
t.Fatal("Cannot create cookiejar", err)
}
c := pester.New()
c := New()
c.Jar = jar
url := fmt.Sprintf("http://localhost:%d", port)
@@ -221,7 +300,7 @@ func TestEmbeddedClientTimeout(t *testing.T) {
hc := http.DefaultClient
hc.Timeout = clientTimeout
c := pester.NewExtendedClient(hc)
c := NewExtendedClient(hc)
_, err = c.Get(fmt.Sprintf("http://localhost:%d/", port))
if err == nil {
t.Error("expected a timeout error, did not get it")
@@ -230,7 +309,7 @@ func TestEmbeddedClientTimeout(t *testing.T) {
func TestConcurrentRequestsNotRacyAndDontLeak_FailedRequest(t *testing.T) {
goroStart := runtime.NumGoroutine()
c := pester.New()
c := New()
port, err := cookieServer()
if err != nil {
t.Fatalf("unable to start server %v", err)
@@ -270,14 +349,14 @@ func TestConcurrentRequestsNotRacyAndDontLeak_FailedRequest(t *testing.T) {
// give background goroutines time to clean up
<-time.After(1000 * time.Millisecond)
goroEnd := runtime.NumGoroutine()
if goroStart != goroEnd {
if goroStart < goroEnd {
t.Errorf("got %d running goroutines, want %d", goroEnd, goroStart)
}
}
func TestConcurrentRequestsNotRacyAndDontLeak_SuccessfulRequest(t *testing.T) {
goroStart := runtime.NumGoroutine()
c := pester.New()
c := New()
nonExistantURL := "http://localhost:9000/foo"
conc := 5
errCh := make(chan error, conc)
@@ -311,9 +390,9 @@ func TestConcurrentRequestsNotRacyAndDontLeak_SuccessfulRequest(t *testing.T) {
wg.Wait()
// give background goroutines time to clean up
<-time.After(250 * time.Millisecond)
<-time.After(1000 * time.Millisecond)
goroEnd := runtime.NumGoroutine()
if goroStart != goroEnd {
if goroStart < goroEnd {
t.Errorf("got %d running goroutines, want %d", goroEnd, goroStart)
}
}
@@ -343,7 +422,13 @@ func cookieServer() (int, error) {
log.Fatalf("slow-server error %v", err)
}
}()
port, err := strconv.Atoi(strings.Replace(l.Addr().String(), "[::]:", "", 1))
var port int
_, sport, err := net.SplitHostPort(l.Addr().String())
if err == nil {
port, err = strconv.Atoi(sport)
}
if err != nil {
return -1, fmt.Errorf("unable to determine port %v", err)
}
@@ -365,9 +450,16 @@ func timeoutServer(timeout time.Duration) (int, error) {
log.Fatalf("slow-server error %v", err)
}
}()
port, err := strconv.Atoi(strings.Replace(l.Addr().String(), "[::]:", "", 1))
var port int
_, sport, err := net.SplitHostPort(l.Addr().String())
if err == nil {
port, err = strconv.Atoi(sport)
}
if err != nil {
return -1, fmt.Errorf("unable to determine port %v", err)
}
return port, nil
}
+17
View File
@@ -102,6 +102,23 @@ func main() {
log.Printf("POST :%d %s\n\n", port, resp.Status)
}
log.Println("> pester.Post with retries to non-existant url")
{
client := pester.New()
client.MaxRetries = 3
client.KeepLog = true
_, err := client.Post("http://localhost:9001", "application/json", strings.NewReader(`{"json":true}`))
if err == nil {
log.Printf("expected to error after max retries of 3")
}
if len(client.ErrLog) != 3 {
log.Fatalf("expected 3 error logs, got %d: %v", len(client.ErrLog), client.ErrLog)
}
log.Printf("POST: %v\n\n", err)
}
log.Println("> pester.Head with defaults")
{ // use the pester.Head drop in replacement
resp, err := pester.Head(fmt.Sprintf("http://localhost:%d", port))