├── Godeps ├── _workspace │ ├── .gitignore │ └── src │ │ └── github.com │ │ └── cenkalti │ │ └── backoff │ │ ├── .travis.yml │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── retry.go │ │ ├── ticker.go │ │ ├── backoff.go │ │ ├── README.md │ │ └── exponential.go ├── Readme └── Godeps.json ├── .travis.yml ├── readme_assets └── jake_amazed.gif ├── Makefile ├── .gitignore ├── requests.go ├── README.md ├── api.go └── requests_test.go /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.3.3 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - go1.6 5 | - tip 6 | 7 | install: make godep 8 | -------------------------------------------------------------------------------- /readme_assets/jake_amazed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonmaia/requests/HEAD/readme_assets/jake_amazed.gif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | godep: 3 | go get github.com/tools/godep 4 | godep restore ./... 5 | 6 | test: godep 7 | godep go test ./... 8 | 9 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/leonmaia/requests", 3 | "GoVersion": "go1.5.1", 4 | "Deps": [ 5 | { 6 | "ImportPath": "github.com/cenkalti/backoff", 7 | "Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cenk Altı 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /requests.go: -------------------------------------------------------------------------------- 1 | // Package requests implements functions to manipulate requests. 2 | package requests 3 | 4 | import ( 5 | "io" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/cenkalti/backoff" 10 | ) 11 | 12 | const ( 13 | // Default quantity of retries 14 | Retries = 3 15 | // Default timeout is 30 seconds 16 | Timeout = 30 * time.Second 17 | ) 18 | 19 | // NewRequest returns an Request with exponential backoff as default. 20 | func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { 21 | req, err := http.NewRequest(method, urlStr, body) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &Request{ 27 | req, Retries, Timeout, backoff.NewExponentialBackOff()}, 28 | nil 29 | } 30 | 31 | // Request type. 32 | type Request struct { 33 | *http.Request 34 | retry int 35 | timeout time.Duration 36 | backoff *backoff.ExponentialBackOff // Default Type of backoff. 37 | } 38 | 39 | // Set the amount of retries 40 | func (r *Request) Retries(times int) *Request { 41 | r.retry = times 42 | return r 43 | } 44 | 45 | // Timeout specifies a time limit for requests made by the Client. 46 | // A Timeout of zero means no timeout. 47 | func (r *Request) Timeout(t time.Duration) *Request { 48 | r.timeout = t 49 | return r 50 | } 51 | 52 | // New Client with timeout 53 | func (r *Request) newClient() *http.Client { 54 | return &http.Client{Timeout: r.timeout} 55 | } 56 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import "time" 4 | 5 | // An Operation is executing by Retry() or RetryNotify(). 6 | // The operation will be retried using a backoff policy if it returns an error. 7 | type Operation func() error 8 | 9 | // Notify is a notify-on-error function. It receives an operation error and 10 | // backoff delay if the operation failed (with an error). 11 | // 12 | // NOTE that if the backoff policy stated to stop retrying, 13 | // the notify function isn't called. 14 | type Notify func(error, time.Duration) 15 | 16 | // Retry the function f until it does not return error or BackOff stops. 17 | // f is guaranteed to be run at least once. 18 | // It is the caller's responsibility to reset b after Retry returns. 19 | // 20 | // Retry sleeps the goroutine for the duration returned by BackOff after a 21 | // failed operation returns. 22 | func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) } 23 | 24 | // RetryNotify calls notify function with the error and wait duration 25 | // for each failed attempt before sleep. 26 | func RetryNotify(operation Operation, b BackOff, notify Notify) error { 27 | var err error 28 | var next time.Duration 29 | 30 | b.Reset() 31 | for { 32 | if err = operation(); err == nil { 33 | return nil 34 | } 35 | 36 | if next = b.NextBackOff(); next == Stop { 37 | return err 38 | } 39 | 40 | if notify != nil { 41 | notify(err, next) 42 | } 43 | 44 | time.Sleep(next) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Requests 2 | 3 | [![Join the chat at https://gitter.im/leonmaia/requests](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/leonmaia/requests?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/pkg/github.com/leonmaia/requests) 5 | [![Build Status](https://travis-ci.org/leonmaia/requests.svg?branch=master)](https://travis-ci.org/leonmaia/requests) 6 | 7 | Most existing requests packages I've seen reimplement all features requests offers. This Request inherits all the behavior and functions of [http.Requests](https://godoc.org/net/http#Request) package and adds others functions and behaviors. 8 | 9 | ![amazing](https://raw.github.com/leonmaia/requests/master/readme_assets/jake_amazed.gif) 10 | 11 | 12 | Features 13 | -------- 14 | 15 | - Retries 16 | - Connection Timeouts 17 | - byte and json streams 18 | 19 | Installation 20 | ------------ 21 | 22 | To install Requests, simply: 23 | 24 | $ go get github.com/leonmaia/requests 25 | 26 | 27 | Usage 28 | ------------ 29 | ```go 30 | package whatever 31 | 32 | import ( 33 | "github.com/leonmaia/requests" 34 | ) 35 | 36 | func GetExampleWithDefaultTimeoutAndRetries() error { 37 | r, err := requests.NewRequest("GET", "http://google.com", nil) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | response, err := r.Do() 43 | if err != nil { 44 | return err 45 | } 46 | // Do whatever you want with the response 47 | return nil 48 | } 49 | ``` 50 | How to Contribute 51 | ------ 52 | 53 | I strongly encourage everyone whom creates a usefull custom assertion function 54 | to contribute them and help make this package even better. 55 | 56 | Make sure all the tests are passing, and that you have covered all the changes 57 | you made. 58 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff. 10 | // 11 | // Ticks will continue to arrive when the previous operation is still running, 12 | // so operations that take a while to fail could run in quick succession. 13 | type Ticker struct { 14 | C <-chan time.Time 15 | c chan time.Time 16 | b BackOff 17 | stop chan struct{} 18 | stopOnce sync.Once 19 | } 20 | 21 | // NewTicker returns a new Ticker containing a channel that will send the time at times 22 | // specified by the BackOff argument. Ticker is guaranteed to tick at least once. 23 | // The channel is closed when Stop method is called or BackOff stops. 24 | func NewTicker(b BackOff) *Ticker { 25 | c := make(chan time.Time) 26 | t := &Ticker{ 27 | C: c, 28 | c: c, 29 | b: b, 30 | stop: make(chan struct{}), 31 | } 32 | go t.run() 33 | runtime.SetFinalizer(t, (*Ticker).Stop) 34 | return t 35 | } 36 | 37 | // Stop turns off a ticker. After Stop, no more ticks will be sent. 38 | func (t *Ticker) Stop() { 39 | t.stopOnce.Do(func() { close(t.stop) }) 40 | } 41 | 42 | func (t *Ticker) run() { 43 | c := t.c 44 | defer close(c) 45 | t.b.Reset() 46 | 47 | // Ticker is guaranteed to tick at least once. 48 | afterC := t.send(time.Now()) 49 | 50 | for { 51 | if afterC == nil { 52 | return 53 | } 54 | 55 | select { 56 | case tick := <-afterC: 57 | afterC = t.send(tick) 58 | case <-t.stop: 59 | t.c = nil // Prevent future ticks from being sent to the channel. 60 | return 61 | } 62 | } 63 | } 64 | 65 | func (t *Ticker) send(tick time.Time) <-chan time.Time { 66 | select { 67 | case t.c <- tick: 68 | case <-t.stop: 69 | return nil 70 | } 71 | 72 | next := t.b.NextBackOff() 73 | if next == Stop { 74 | t.Stop() 75 | return nil 76 | } 77 | 78 | return time.After(next) 79 | } 80 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go: -------------------------------------------------------------------------------- 1 | // Package backoff implements backoff algorithms for retrying operations. 2 | // 3 | // Also has a Retry() helper for retrying operations that may fail. 4 | package backoff 5 | 6 | import "time" 7 | 8 | // BackOff is a backoff policy for retrying an operation. 9 | type BackOff interface { 10 | // NextBackOff returns the duration to wait before retrying the operation, 11 | // or backoff.Stop to indicate that no more retries should be made. 12 | // 13 | // Example usage: 14 | // 15 | // duration := backoff.NextBackOff(); 16 | // if (duration == backoff.Stop) { 17 | // // Do not retry operation. 18 | // } else { 19 | // // Sleep for duration and retry operation. 20 | // } 21 | // 22 | NextBackOff() time.Duration 23 | 24 | // Reset to initial state. 25 | Reset() 26 | } 27 | 28 | // Indicates that no more retries should be made for use in NextBackOff(). 29 | const Stop time.Duration = -1 30 | 31 | // ZeroBackOff is a fixed backoff policy whose backoff time is always zero, 32 | // meaning that the operation is retried immediately without waiting, indefinitely. 33 | type ZeroBackOff struct{} 34 | 35 | func (b *ZeroBackOff) Reset() {} 36 | 37 | func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 } 38 | 39 | // StopBackOff is a fixed backoff policy that always returns backoff.Stop for 40 | // NextBackOff(), meaning that the operation should never be retried. 41 | type StopBackOff struct{} 42 | 43 | func (b *StopBackOff) Reset() {} 44 | 45 | func (b *StopBackOff) NextBackOff() time.Duration { return Stop } 46 | 47 | // ConstantBackOff is a backoff policy that always returns the same backoff delay. 48 | // This is in contrast to an exponential backoff policy, 49 | // which returns a delay that grows longer as you call NextBackOff() over and over again. 50 | type ConstantBackOff struct { 51 | Interval time.Duration 52 | } 53 | 54 | func (b *ConstantBackOff) Reset() {} 55 | func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval } 56 | 57 | func NewConstantBackOff(d time.Duration) *ConstantBackOff { 58 | return &ConstantBackOff{Interval: d} 59 | } 60 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | 13 | "github.com/cenkalti/backoff" 14 | ) 15 | 16 | func doReq(r *Request, c *http.Client) (*http.Response, error) { 17 | res, err := c.Do(r.Request) 18 | if err != nil && r.retry > 0 { 19 | r.retry-- 20 | return nil, err 21 | } 22 | if res != nil && res.StatusCode >= 500 && res.StatusCode <= 599 && r.retry > 0 { 23 | r.retry-- 24 | return nil, errors.New("Server Error") 25 | } 26 | 27 | return res, nil 28 | } 29 | 30 | func ToBytes(res *http.Response) ([]byte, error) { 31 | if res != nil { 32 | defer res.Body.Close() 33 | body, err := ioutil.ReadAll(res.Body) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return body, nil 39 | } 40 | return nil, errors.New("Invalid response") 41 | } 42 | 43 | func ToByteChannel(res *http.Response, bufferSize int) <-chan []byte { 44 | resp_chan := make(chan []byte) 45 | b := bufio.NewReader(res.Body) 46 | buf := make([]byte, 0, bufferSize) 47 | go func() { 48 | for { 49 | n, err := b.Read(buf[:cap(buf)]) 50 | buf = buf[:n] 51 | if n == 0 { 52 | if err == nil { 53 | continue 54 | } 55 | if err == io.EOF { 56 | close(resp_chan) 57 | break 58 | } 59 | log.Fatal(err) 60 | } 61 | resp_chan <- buf 62 | } 63 | }() 64 | return resp_chan 65 | } 66 | 67 | func ToJsonChannel(r *http.Response, dec interface{}) <-chan interface{} { 68 | resp_chan := make(chan interface{}) 69 | decoder := json.NewDecoder(r.Body) 70 | go func() { 71 | for { 72 | err := decoder.Decode(dec) 73 | if err == nil { 74 | resp_chan <- dec 75 | } else if err == io.EOF { 76 | defer close(resp_chan) 77 | break 78 | } else if err != nil { 79 | fmt.Println(err) 80 | defer close(resp_chan) 81 | break 82 | } 83 | } 84 | }() 85 | 86 | return resp_chan 87 | } 88 | 89 | // Do should be called when the Request is fully configured. 90 | func (r *Request) Do() (*http.Response, error) { 91 | c := r.newClient() 92 | res, err := doReq(r, c) 93 | if err != nil { 94 | op := r.operation(c) 95 | err = backoff.Retry(op, r.backoff) 96 | if err != nil { 97 | return nil, err 98 | } 99 | } 100 | 101 | return res, nil 102 | } 103 | 104 | func (r *Request) operation(c *http.Client) func() error { 105 | return func() error { 106 | _, err := doReq(r, c) 107 | return err 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /requests_test.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestShouldBeAbleToSetQuantityOfRetries(t *testing.T) { 14 | ts := httptest.NewServer( 15 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | w.WriteHeader(http.StatusInternalServerError) 17 | }), 18 | ) 19 | defer ts.Close() 20 | 21 | if req, _ := NewRequest("GET", ts.URL, nil); req.retry != Retries { 22 | t.Error(fmt.Sprintf("retry should've been set to %d, got %d", Retries, req.retry)) 23 | } 24 | } 25 | 26 | func TestShouldBeAbleToSetURL(t *testing.T) { 27 | ts := httptest.NewServer( 28 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 29 | w.WriteHeader(http.StatusInternalServerError) 30 | }), 31 | ) 32 | defer ts.Close() 33 | 34 | if req, _ := NewRequest("GET", ts.URL, nil); req.URL.String() != ts.URL { 35 | t.Error(fmt.Sprintf("url should've been set to %s, got %s", ts.URL, req.URL.String())) 36 | } 37 | } 38 | 39 | func TestShouldRetryAfterResponseCode5XX(t *testing.T) { 40 | ts := httptest.NewServer( 41 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 | w.WriteHeader(http.StatusInternalServerError) 43 | }), 44 | ) 45 | defer ts.Close() 46 | 47 | req, _ := NewRequest("GET", ts.URL, nil) 48 | req.Do() 49 | 50 | if req.retry != 0 { 51 | t.Error(fmt.Sprintf("retry should be 0, got %d", req.retry)) 52 | } 53 | } 54 | 55 | func TestShouldRetryWhenErrorHappens(t *testing.T) { 56 | ts := httptest.NewServer( 57 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 58 | errors.New("emit macho dwarf: elf header corrupted") 59 | }), 60 | ) 61 | defer ts.Close() 62 | 63 | req, _ := NewRequest("GET", "http://www.qoroqer.com", nil) 64 | req.Do() 65 | 66 | if req.retry != 0 { 67 | t.Error(fmt.Sprintf("retry should be 0, got %d", req.retry)) 68 | } 69 | } 70 | 71 | func TestShouldKeepRetryCountIntactWhenOK(t *testing.T) { 72 | numbers := []int{1, 2, 3, 4, 5} 73 | ts := httptest.NewServer( 74 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 75 | js, _ := json.Marshal(&numbers) 76 | w.Write(js) 77 | }), 78 | ) 79 | defer ts.Close() 80 | 81 | req, _ := NewRequest("GET", ts.URL, nil) 82 | req.Do() 83 | 84 | if req.retry != Retries { 85 | t.Error(fmt.Sprintf("retry should not have changed from %d, to %d", Retries, req.retry)) 86 | } 87 | } 88 | 89 | func TestShouldRetrieWhenTimeout(t *testing.T) { 90 | ts := httptest.NewServer( 91 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 92 | time.Sleep(50 * time.Millisecond) 93 | w.WriteHeader(http.StatusOK) 94 | }), 95 | ) 96 | defer ts.Close() 97 | 98 | req, _ := NewRequest("GET", ts.URL, nil) 99 | req.Timeout(1 * time.Nanosecond) 100 | 101 | req.Do() 102 | 103 | if req.retry != 0 { 104 | t.Error(fmt.Sprintf("retry should be 0, got %d", req.retry)) 105 | } 106 | } 107 | 108 | func TestResponseShouldNotBeNilWhenOK(t *testing.T) { 109 | numbers := []int{1, 2, 3, 4, 5} 110 | ts := httptest.NewServer( 111 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 112 | js, _ := json.Marshal(&numbers) 113 | w.Write(js) 114 | }), 115 | ) 116 | defer ts.Close() 117 | 118 | req, _ := NewRequest("GET", ts.URL, nil) 119 | req.Do() 120 | 121 | if resp, _ := req.Do(); resp == nil { 122 | t.Error("response should be nil") 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/README.md: -------------------------------------------------------------------------------- 1 | # Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] 2 | 3 | This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client]. 4 | 5 | [Exponential backoff][exponential backoff wiki] 6 | is an algorithm that uses feedback to multiplicatively decrease the rate of some process, 7 | in order to gradually find an acceptable rate. 8 | The retries exponentially increase and stop increasing when a certain threshold is met. 9 | 10 | ## How To 11 | 12 | We define two functions, `Retry()` and `RetryNotify()`. 13 | They receive an `Operation` to execute, a `BackOff` algorithm, 14 | and an optional `Notify` error handler. 15 | 16 | The operation will be executed, and will be retried on failure with delay 17 | as given by the backoff algorithm. The backoff algorithm can also decide when to stop 18 | retrying. 19 | In addition, the notify error handler will be called after each failed attempt, 20 | except for the last time, whose error should be handled by the caller. 21 | 22 | ```go 23 | // An Operation is executing by Retry() or RetryNotify(). 24 | // The operation will be retried using a backoff policy if it returns an error. 25 | type Operation func() error 26 | 27 | // Notify is a notify-on-error function. It receives an operation error and 28 | // backoff delay if the operation failed (with an error). 29 | // 30 | // NOTE that if the backoff policy stated to stop retrying, 31 | // the notify function isn't called. 32 | type Notify func(error, time.Duration) 33 | 34 | func Retry(Operation, BackOff) error 35 | func RetryNotify(Operation, BackOff, Notify) 36 | ``` 37 | 38 | ## Examples 39 | 40 | See more advanced examples in the [godoc][advanced example]. 41 | 42 | ### Retry 43 | 44 | Simple retry helper that uses the default exponential backoff algorithm: 45 | 46 | ```go 47 | operation := func() error { 48 | // An operation that might fail. 49 | return nil // or return errors.New("some error") 50 | } 51 | 52 | err := Retry(operation, NewExponentialBackOff()) 53 | if err != nil { 54 | // Handle error. 55 | return err 56 | } 57 | 58 | // Operation is successful. 59 | return nil 60 | ``` 61 | 62 | ### Ticker 63 | 64 | ```go 65 | operation := func() error { 66 | // An operation that might fail 67 | return nil // or return errors.New("some error") 68 | } 69 | 70 | b := NewExponentialBackOff() 71 | ticker := NewTicker(b) 72 | 73 | var err error 74 | 75 | // Ticks will continue to arrive when the previous operation is still running, 76 | // so operations that take a while to fail could run in quick succession. 77 | for range ticker.C { 78 | if err = operation(); err != nil { 79 | log.Println(err, "will retry...") 80 | continue 81 | } 82 | 83 | ticker.Stop() 84 | break 85 | } 86 | 87 | if err != nil { 88 | // Operation has failed. 89 | return err 90 | } 91 | 92 | // Operation is successful. 93 | return nil 94 | ``` 95 | 96 | ## Getting Started 97 | 98 | ```bash 99 | # install 100 | $ go get github.com/cenkalti/backoff 101 | 102 | # test 103 | $ cd $GOPATH/src/github.com/cenkalti/backoff 104 | $ go get -t ./... 105 | $ go test -v -cover 106 | ``` 107 | 108 | [godoc]: https://godoc.org/github.com/cenkalti/backoff 109 | [godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png 110 | [travis]: https://travis-ci.org/cenkalti/backoff 111 | [travis image]: https://travis-ci.org/cenkalti/backoff.png 112 | 113 | [google-http-java-client]: https://github.com/google/google-http-java-client 114 | [exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff 115 | 116 | [advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_ 117 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | /* 9 | ExponentialBackOff is a backoff implementation that increases the backoff 10 | period for each retry attempt using a randomization function that grows exponentially. 11 | 12 | NextBackOff() is calculated using the following formula: 13 | 14 | randomized interval = 15 | RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) 16 | 17 | In other words NextBackOff() will range between the randomization factor 18 | percentage below and above the retry interval. 19 | 20 | For example, given the following parameters: 21 | 22 | RetryInterval = 2 23 | RandomizationFactor = 0.5 24 | Multiplier = 2 25 | 26 | the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, 27 | multiplied by the exponential, that is, between 2 and 6 seconds. 28 | 29 | Note: MaxInterval caps the RetryInterval and not the randomized interval. 30 | 31 | If the time elapsed since an ExponentialBackOff instance is created goes past the 32 | MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop. 33 | 34 | The elapsed time can be reset by calling Reset(). 35 | 36 | Example: Given the following default arguments, for 10 tries the sequence will be, 37 | and assuming we go over the MaxElapsedTime on the 10th try: 38 | 39 | Request # RetryInterval (seconds) Randomized Interval (seconds) 40 | 41 | 1 0.5 [0.25, 0.75] 42 | 2 0.75 [0.375, 1.125] 43 | 3 1.125 [0.562, 1.687] 44 | 4 1.687 [0.8435, 2.53] 45 | 5 2.53 [1.265, 3.795] 46 | 6 3.795 [1.897, 5.692] 47 | 7 5.692 [2.846, 8.538] 48 | 8 8.538 [4.269, 12.807] 49 | 9 12.807 [6.403, 19.210] 50 | 10 19.210 backoff.Stop 51 | 52 | Note: Implementation is not thread-safe. 53 | */ 54 | type ExponentialBackOff struct { 55 | InitialInterval time.Duration 56 | RandomizationFactor float64 57 | Multiplier float64 58 | MaxInterval time.Duration 59 | // After MaxElapsedTime the ExponentialBackOff stops. 60 | // It never stops if MaxElapsedTime == 0. 61 | MaxElapsedTime time.Duration 62 | Clock Clock 63 | 64 | currentInterval time.Duration 65 | startTime time.Time 66 | } 67 | 68 | // Clock is an interface that returns current time for BackOff. 69 | type Clock interface { 70 | Now() time.Time 71 | } 72 | 73 | // Default values for ExponentialBackOff. 74 | const ( 75 | DefaultInitialInterval = 500 * time.Millisecond 76 | DefaultRandomizationFactor = 0.5 77 | DefaultMultiplier = 1.5 78 | DefaultMaxInterval = 60 * time.Second 79 | DefaultMaxElapsedTime = 15 * time.Minute 80 | ) 81 | 82 | // NewExponentialBackOff creates an instance of ExponentialBackOff using default values. 83 | func NewExponentialBackOff() *ExponentialBackOff { 84 | b := &ExponentialBackOff{ 85 | InitialInterval: DefaultInitialInterval, 86 | RandomizationFactor: DefaultRandomizationFactor, 87 | Multiplier: DefaultMultiplier, 88 | MaxInterval: DefaultMaxInterval, 89 | MaxElapsedTime: DefaultMaxElapsedTime, 90 | Clock: SystemClock, 91 | } 92 | b.Reset() 93 | return b 94 | } 95 | 96 | type systemClock struct{} 97 | 98 | func (t systemClock) Now() time.Time { 99 | return time.Now() 100 | } 101 | 102 | // SystemClock implements Clock interface that uses time.Now(). 103 | var SystemClock = systemClock{} 104 | 105 | // Reset the interval back to the initial retry interval and restarts the timer. 106 | func (b *ExponentialBackOff) Reset() { 107 | b.currentInterval = b.InitialInterval 108 | b.startTime = b.Clock.Now() 109 | } 110 | 111 | // NextBackOff calculates the next backoff interval using the formula: 112 | // Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval) 113 | func (b *ExponentialBackOff) NextBackOff() time.Duration { 114 | // Make sure we have not gone over the maximum elapsed time. 115 | if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime { 116 | return Stop 117 | } 118 | defer b.incrementCurrentInterval() 119 | return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) 120 | } 121 | 122 | // GetElapsedTime returns the elapsed time since an ExponentialBackOff instance 123 | // is created and is reset when Reset() is called. 124 | // 125 | // The elapsed time is computed using time.Now().UnixNano(). 126 | func (b *ExponentialBackOff) GetElapsedTime() time.Duration { 127 | return b.Clock.Now().Sub(b.startTime) 128 | } 129 | 130 | // Increments the current interval by multiplying it with the multiplier. 131 | func (b *ExponentialBackOff) incrementCurrentInterval() { 132 | // Check for overflow, if overflow is detected set the current interval to the max interval. 133 | if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { 134 | b.currentInterval = b.MaxInterval 135 | } else { 136 | b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) 137 | } 138 | } 139 | 140 | // Returns a random value from the following interval: 141 | // [randomizationFactor * currentInterval, randomizationFactor * currentInterval]. 142 | func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { 143 | var delta = randomizationFactor * float64(currentInterval) 144 | var minInterval = float64(currentInterval) - delta 145 | var maxInterval = float64(currentInterval) + delta 146 | 147 | // Get a random value from the range [minInterval, maxInterval]. 148 | // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then 149 | // we want a 33% chance for selecting either 1, 2 or 3. 150 | return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) 151 | } 152 | --------------------------------------------------------------------------------