~abp998/gwacl/subscription

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Copyright 2013 Canonical Ltd.  This software is licensed under the
// GNU Lesser General Public License version 3 (see the file COPYING).

package gwacl

import (
    forkedHttp "launchpad.net/gwacl/fork/http"
    "net/http"
    "time"
)

// A RetryPolicy object encapsulates all the information needed to define how
// requests should be retried when particular response codes are returned by
// the Windows Azure server.
type RetryPolicy struct {
    // The number of times a request could be retried.  This does not account
    // for the initial request so a value of 3 means that the request might be
    // performed 4 times in total.
    NbRetries int
    // The HTTP status codes of the response for which the request should be
    // retried.
    HttpStatusCodes []int
    // How long the client should wait between retries.
    Delay time.Duration
}

var (
    NoRetryPolicy = RetryPolicy{NbRetries: 0}
)

// isRetryCode returns whether or not the given http status code indicates that
// the request should be retried according to this policy.
func (policy RetryPolicy) isRetryCode(httpStatusCode int) bool {
    for _, code := range policy.HttpStatusCodes {
        if code == httpStatusCode {
            return true
        }
    }
    return false
}

func (policy RetryPolicy) getRetryHelper() *retryHelper {
    return &retryHelper{retriesLeft: policy.NbRetries, policy: &policy}
}

// A retryHelper is a utility object used to enforce a retry policy when
// performing requests.
type retryHelper struct {
    // The maximum number of retries left to perform.
    retriesLeft int
    // The `RetryPolicy` enforced by this retrier.
    policy *RetryPolicy
}

// shouldRetry returns whether or not a request governed by the underlying
// retry policy should be retried.  When it returns 'true', `shouldRetry` also
// waits for the specified amount of time, as dictated by the retry policy.
func (ret *retryHelper) shouldRetry(httpStatusCode int) bool {
    if ret.retriesLeft > 0 && ret.policy.isRetryCode(httpStatusCode) {
        ret.retriesLeft--
        return true
    }
    return false
}

// A retrier is a struct used to repeat a request as governed by a retry
// policy.  retrier is usually created using RetryPolicy.getRetrier().
type retrier struct {
    *retryHelper

    // The client used to perform requests.
    client *http.Client
}

func (ret *retrier) RetryRequest(request *http.Request) (*http.Response, error) {
    for {
        response, err := ret.client.Do(request)
        if err != nil {
            return nil, err
        }
        if !ret.shouldRetry(response.StatusCode) {
            return response, nil
        }
        time.Sleep(ret.policy.Delay)
    }
}

// getRetrier returns a `retrier` object used to enforce the retry policy.
func (policy RetryPolicy) getRetrier(client *http.Client) *retrier {
    helper := policy.getRetryHelper()
    return &retrier{retryHelper: helper, client: client}
}

// A forkedHttpRetrier is a struct used to repeat a request as governed by a
// retry policy.  forkedHttpRetrier is usually created using
// RetryPolicy.getForkedHttpRetrier().  It's the same as the `retrier` struct
// except it deals with the forked version of the http package.
type forkedHttpRetrier struct {
    *retryHelper

    // The client used to perform requests.
    client *forkedHttp.Client
}

func (ret *forkedHttpRetrier) RetryRequest(request *forkedHttp.Request) (*forkedHttp.Response, error) {
    for {
        response, err := ret.client.Do(request)
        if err != nil {
            return nil, err
        }
        if !ret.shouldRetry(response.StatusCode) {
            return response, nil
        }
        time.Sleep(ret.policy.Delay)
    }
}

// getRetrier returns a `retrier` object used to enforce the retry policy.
func (policy RetryPolicy) getForkedHttpRetrier(client *forkedHttp.Client) *forkedHttpRetrier {
    helper := policy.getRetryHelper()
    return &forkedHttpRetrier{retryHelper: helper, client: client}
}