You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

125 lines
3.0 KiB

  1. package aws
  2. import (
  3. "math"
  4. "net"
  5. "net/http"
  6. "time"
  7. )
  8. type RetryableFunc func(*http.Request, *http.Response, error) bool
  9. type WaitFunc func(try int)
  10. type DeadlineFunc func() time.Time
  11. type ResilientTransport struct {
  12. // Timeout is the maximum amount of time a dial will wait for
  13. // a connect to complete.
  14. //
  15. // The default is no timeout.
  16. //
  17. // With or without a timeout, the operating system may impose
  18. // its own earlier timeout. For instance, TCP timeouts are
  19. // often around 3 minutes.
  20. DialTimeout time.Duration
  21. // MaxTries, if non-zero, specifies the number of times we will retry on
  22. // failure. Retries are only attempted for temporary network errors or known
  23. // safe failures.
  24. MaxTries int
  25. Deadline DeadlineFunc
  26. ShouldRetry RetryableFunc
  27. Wait WaitFunc
  28. transport *http.Transport
  29. }
  30. // Convenience method for creating an http client
  31. func NewClient(rt *ResilientTransport) *http.Client {
  32. rt.transport = &http.Transport{
  33. Dial: func(netw, addr string) (net.Conn, error) {
  34. c, err := net.DialTimeout(netw, addr, rt.DialTimeout)
  35. if err != nil {
  36. return nil, err
  37. }
  38. c.SetDeadline(rt.Deadline())
  39. return c, nil
  40. },
  41. Proxy: http.ProxyFromEnvironment,
  42. }
  43. // TODO: Would be nice is ResilientTransport allowed clients to initialize
  44. // with http.Transport attributes.
  45. return &http.Client{
  46. Transport: rt,
  47. }
  48. }
  49. var retryingTransport = &ResilientTransport{
  50. Deadline: func() time.Time {
  51. return time.Now().Add(5 * time.Second)
  52. },
  53. DialTimeout: 10 * time.Second,
  54. MaxTries: 3,
  55. ShouldRetry: awsRetry,
  56. Wait: ExpBackoff,
  57. }
  58. // Exported default client
  59. var RetryingClient = NewClient(retryingTransport)
  60. func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  61. return t.tries(req)
  62. }
  63. // Retry a request a maximum of t.MaxTries times.
  64. // We'll only retry if the proper criteria are met.
  65. // If a wait function is specified, wait that amount of time
  66. // In between requests.
  67. func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) {
  68. for try := 0; try < t.MaxTries; try += 1 {
  69. res, err = t.transport.RoundTrip(req)
  70. if !t.ShouldRetry(req, res, err) {
  71. break
  72. }
  73. if res != nil {
  74. res.Body.Close()
  75. }
  76. if t.Wait != nil {
  77. t.Wait(try)
  78. }
  79. }
  80. return
  81. }
  82. func ExpBackoff(try int) {
  83. time.Sleep(100 * time.Millisecond *
  84. time.Duration(math.Exp2(float64(try))))
  85. }
  86. func LinearBackoff(try int) {
  87. time.Sleep(time.Duration(try*100) * time.Millisecond)
  88. }
  89. // Decide if we should retry a request.
  90. // In general, the criteria for retrying a request is described here
  91. // http://docs.aws.amazon.com/general/latest/gr/api-retries.html
  92. func awsRetry(req *http.Request, res *http.Response, err error) bool {
  93. retry := false
  94. // Retry if there's a temporary network error.
  95. if neterr, ok := err.(net.Error); ok {
  96. if neterr.Temporary() {
  97. retry = true
  98. }
  99. }
  100. // Retry if we get a 5xx series error.
  101. if res != nil {
  102. if res.StatusCode >= 500 && res.StatusCode < 600 {
  103. retry = true
  104. }
  105. }
  106. return retry
  107. }