mirror of
https://github.com/Threnklyn/jira.git
synced 2026-06-01 10:48:27 +02:00
update dependencies
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user