c76ad94492
This commit adds a build step to travis to auto-delete unstable archives older than 14 days (our regular release schedule) from Azure via ci.go purge. The commit also pulls in the latest Azure storage code, also switching over from the old import path (github.com/Azure/azure-sdk-for-go) to the new split one (github.com/Azure/azure-storage-go).
271 lines
9.8 KiB
Go
271 lines
9.8 KiB
Go
package autorest
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Sender is the interface that wraps the Do method to send HTTP requests.
|
|
//
|
|
// The standard http.Client conforms to this interface.
|
|
type Sender interface {
|
|
Do(*http.Request) (*http.Response, error)
|
|
}
|
|
|
|
// SenderFunc is a method that implements the Sender interface.
|
|
type SenderFunc func(*http.Request) (*http.Response, error)
|
|
|
|
// Do implements the Sender interface on SenderFunc.
|
|
func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
|
|
return sf(r)
|
|
}
|
|
|
|
// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
|
|
// http.Request and pass it along or, first, pass the http.Request along then react to the
|
|
// http.Response result.
|
|
type SendDecorator func(Sender) Sender
|
|
|
|
// CreateSender creates, decorates, and returns, as a Sender, the default http.Client.
|
|
func CreateSender(decorators ...SendDecorator) Sender {
|
|
return DecorateSender(&http.Client{}, decorators...)
|
|
}
|
|
|
|
// DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to
|
|
// the Sender. Decorators are applied in the order received, but their affect upon the request
|
|
// depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a
|
|
// post-decorator (pass the http.Request along and react to the results in http.Response).
|
|
func DecorateSender(s Sender, decorators ...SendDecorator) Sender {
|
|
for _, decorate := range decorators {
|
|
s = decorate(s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Send sends, by means of the default http.Client, the passed http.Request, returning the
|
|
// http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which
|
|
// it will apply the http.Client before invoking the Do method.
|
|
//
|
|
// Send is a convenience method and not recommended for production. Advanced users should use
|
|
// SendWithSender, passing and sharing their own Sender (e.g., instance of http.Client).
|
|
//
|
|
// Send will not poll or retry requests.
|
|
func Send(r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
|
|
return SendWithSender(&http.Client{}, r, decorators...)
|
|
}
|
|
|
|
// SendWithSender sends the passed http.Request, through the provided Sender, returning the
|
|
// http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which
|
|
// it will apply the http.Client before invoking the Do method.
|
|
//
|
|
// SendWithSender will not poll or retry requests.
|
|
func SendWithSender(s Sender, r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
|
|
return DecorateSender(s, decorators...).Do(r)
|
|
}
|
|
|
|
// AfterDelay returns a SendDecorator that delays for the passed time.Duration before
|
|
// invoking the Sender. The delay may be terminated by closing the optional channel on the
|
|
// http.Request. If canceled, no further Senders are invoked.
|
|
func AfterDelay(d time.Duration) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
|
if !DelayForBackoff(d, 0, r.Cancel) {
|
|
return nil, fmt.Errorf("autorest: AfterDelay canceled before full delay")
|
|
}
|
|
return s.Do(r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// AsIs returns a SendDecorator that invokes the passed Sender without modifying the http.Request.
|
|
func AsIs() SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
|
return s.Do(r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoCloseIfError returns a SendDecorator that first invokes the passed Sender after which
|
|
// it closes the response if the passed Sender returns an error and the response body exists.
|
|
func DoCloseIfError() SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
|
resp, err := s.Do(r)
|
|
if err != nil {
|
|
Respond(resp, ByDiscardingBody(), ByClosing())
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoErrorIfStatusCode returns a SendDecorator that emits an error if the response StatusCode is
|
|
// among the set passed. Since these are artificial errors, the response body may still require
|
|
// closing.
|
|
func DoErrorIfStatusCode(codes ...int) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
|
resp, err := s.Do(r)
|
|
if err == nil && ResponseHasStatusCode(resp, codes...) {
|
|
err = NewErrorWithResponse("autorest", "DoErrorIfStatusCode", resp, "%v %v failed with %s",
|
|
resp.Request.Method,
|
|
resp.Request.URL,
|
|
resp.Status)
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoErrorUnlessStatusCode returns a SendDecorator that emits an error unless the response
|
|
// StatusCode is among the set passed. Since these are artificial errors, the response body
|
|
// may still require closing.
|
|
func DoErrorUnlessStatusCode(codes ...int) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
|
resp, err := s.Do(r)
|
|
if err == nil && !ResponseHasStatusCode(resp, codes...) {
|
|
err = NewErrorWithResponse("autorest", "DoErrorUnlessStatusCode", resp, "%v %v failed with %s",
|
|
resp.Request.Method,
|
|
resp.Request.URL,
|
|
resp.Status)
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoPollForStatusCodes returns a SendDecorator that polls if the http.Response contains one of the
|
|
// passed status codes. It expects the http.Response to contain a Location header providing the
|
|
// URL at which to poll (using GET) and will poll until the time passed is equal to or greater than
|
|
// the supplied duration. It will delay between requests for the duration specified in the
|
|
// RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by
|
|
// closing the optional channel on the http.Request.
|
|
func DoPollForStatusCodes(duration time.Duration, delay time.Duration, codes ...int) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
|
resp, err = s.Do(r)
|
|
|
|
if err == nil && ResponseHasStatusCode(resp, codes...) {
|
|
r, err = NewPollingRequest(resp, r.Cancel)
|
|
|
|
for err == nil && ResponseHasStatusCode(resp, codes...) {
|
|
Respond(resp,
|
|
ByDiscardingBody(),
|
|
ByClosing())
|
|
resp, err = SendWithSender(s, r,
|
|
AfterDelay(GetRetryAfter(resp, delay)))
|
|
}
|
|
}
|
|
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoRetryForAttempts returns a SendDecorator that retries a failed request for up to the specified
|
|
// number of attempts, exponentially backing off between requests using the supplied backoff
|
|
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
|
|
// the http.Request.
|
|
func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
|
for attempt := 0; attempt < attempts; attempt++ {
|
|
resp, err = s.Do(r)
|
|
if err == nil {
|
|
return resp, err
|
|
}
|
|
DelayForBackoff(backoff, attempt, r.Cancel)
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoRetryForStatusCodes returns a SendDecorator that retries for specified statusCodes for up to the specified
|
|
// number of attempts, exponentially backing off between requests using the supplied backoff
|
|
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
|
|
// the http.Request.
|
|
func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
|
b := []byte{}
|
|
if r.Body != nil {
|
|
b, err = ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
}
|
|
|
|
// Increment to add the first call (attempts denotes number of retries)
|
|
attempts++
|
|
for attempt := 0; attempt < attempts; attempt++ {
|
|
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
|
resp, err = s.Do(r)
|
|
if err != nil || !ResponseHasStatusCode(resp, codes...) {
|
|
return resp, err
|
|
}
|
|
DelayForBackoff(backoff, attempt, r.Cancel)
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DoRetryForDuration returns a SendDecorator that retries the request until the total time is equal
|
|
// to or greater than the specified duration, exponentially backing off between requests using the
|
|
// supplied backoff time.Duration (which may be zero). Retrying may be canceled by closing the
|
|
// optional channel on the http.Request.
|
|
func DoRetryForDuration(d time.Duration, backoff time.Duration) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
|
end := time.Now().Add(d)
|
|
for attempt := 0; time.Now().Before(end); attempt++ {
|
|
resp, err = s.Do(r)
|
|
if err == nil {
|
|
return resp, err
|
|
}
|
|
DelayForBackoff(backoff, attempt, r.Cancel)
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// WithLogging returns a SendDecorator that implements simple before and after logging of the
|
|
// request.
|
|
func WithLogging(logger *log.Logger) SendDecorator {
|
|
return func(s Sender) Sender {
|
|
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
|
logger.Printf("Sending %s %s", r.Method, r.URL)
|
|
resp, err := s.Do(r)
|
|
if err != nil {
|
|
logger.Printf("%s %s received error '%v'", r.Method, r.URL, err)
|
|
} else {
|
|
logger.Printf("%s %s received %s", r.Method, r.URL, resp.Status)
|
|
}
|
|
return resp, err
|
|
})
|
|
}
|
|
}
|
|
|
|
// DelayForBackoff invokes time.After for the supplied backoff duration raised to the power of
|
|
// passed attempt (i.e., an exponential backoff delay). Backoff duration is in seconds and can set
|
|
// to zero for no delay. The delay may be canceled by closing the passed channel. If terminated early,
|
|
// returns false.
|
|
// Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt
|
|
// count.
|
|
func DelayForBackoff(backoff time.Duration, attempt int, cancel <-chan struct{}) bool {
|
|
select {
|
|
case <-time.After(time.Duration(backoff.Seconds()*math.Pow(2, float64(attempt))) * time.Second):
|
|
return true
|
|
case <-cancel:
|
|
return false
|
|
}
|
|
}
|