Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add retry when timeout #203

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
40 changes: 33 additions & 7 deletions gorequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -947,11 +947,15 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e
for {
resp, body, errs = s.getResponseBytes()
if errs != nil {
return nil, nil, errs
}
if s.isRetryableRequest(resp) {
resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt))
break
if s.isErrNotRetryableRequest(errs) {
errs = append(errs, fmt.Errorf("retry attempt: %d", s.Retryable.Attempt))
return nil, nil, errs
}
} else {
if s.isRetryableRequest(resp, errs) {
resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt))
break
}
}
}

Expand All @@ -962,15 +966,25 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e
return resp, body, nil
}

func (s *SuperAgent) isRetryableRequest(resp Response) bool {
if s.Retryable.Enable && s.Retryable.Attempt < s.Retryable.RetryerCount && contains(resp.StatusCode, s.Retryable.RetryableStatus) {
func (s *SuperAgent) isRetryableRequest(resp Response, errs []error) bool {
if s.Retryable.Enable && s.Retryable.Attempt < s.Retryable.RetryerCount && (contains(resp.StatusCode, s.Retryable.RetryableStatus)) {
time.Sleep(s.Retryable.RetryerTime)
s.Retryable.Attempt++
return false
}
return true
}

func (s *SuperAgent) isErrNotRetryableRequest(errs []error) bool {
if s.Retryable.Enable && s.Retryable.Attempt < s.Retryable.RetryerCount && IsTimeout(errs) {
time.Sleep(s.Retryable.RetryerTime)
s.Retryable.Attempt++
s.Errors = nil
return false
}
return true
}

func contains(respStatus int, statuses []int) bool {
for _, status := range statuses {
if status == respStatus {
Expand All @@ -980,6 +994,18 @@ func contains(respStatus int, statuses []int) bool {
return false
}

// IsTimeout checks is it a net timeout error.
// Applicable only for go version >1.6
func IsTimeout(errs []error) bool {
for _, v := range errs {
if err, ok := v.(net.Error); ok && err.Timeout() {
return true
}
}

return false
}

// EndStruct should be used when you want the body as a struct. The callbacks work the same way as with `End`, except that a struct is used instead of a string.
func (s *SuperAgent) EndStruct(v interface{}, callback ...func(response Response, v interface{}, body []byte, errs []error)) (Response, []byte, []error) {
resp, body, errs := s.EndBytes()
Expand Down
46 changes: 43 additions & 3 deletions gorequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,12 @@ func TestGet(t *testing.T) {
// testing for Get method with retry option
func TestRetryGet(t *testing.T) {
const (
case1_empty = "/"
case24_after_3_attempt_return_valid = "/retry_3_attempt_then_valid"
retry_count_expected = "3"
case1_empty = "/"
case24_after_3_attempt_return_valid = "/retry_3_attempt_then_valid"
retry_count_expected = "3"
casetimeout_after_3_attempt_return_valid = "/timeout_after_3_attempt_return_valid"
timeout_retry_expected = "retry attempt: 3"
casetimeout_retry_3_attempt = "/imeout_retry_3_attempt"
)

var attempt int
Expand Down Expand Up @@ -235,6 +238,16 @@ func TestRetryGet(t *testing.T) {
t.Logf("case %v ", case24_after_3_attempt_return_valid)
}
attempt++
case casetimeout_retry_3_attempt:
time.Sleep(5 * time.Nanosecond)
attempt++
case casetimeout_after_3_attempt_return_valid:
if attempt == 4 {
w.WriteHeader(200)
} else {
time.Sleep(2 * time.Second)
}
attempt++
}

}))
Expand Down Expand Up @@ -264,6 +277,33 @@ func TestRetryGet(t *testing.T) {
if retryCountReturn != retry_count_expected {
t.Errorf("Expected [%s] retry but was [%s]", retry_count_expected, retryCountReturn)
}

// Timeout retry 3 times
_, _, errs = New().Get(ts.URL+casetimeout_retry_3_attempt).
Timeout(1*time.Nanosecond).
Retry(3, 1*time.Nanosecond, http.StatusBadRequest).
End()
if errs != nil {
lastErr := errs[len(errs)-1]
if lastErr.Error() != timeout_retry_expected {
t.Errorf("Expected [%s] retry but was [%s]", timeout_retry_expected, lastErr)
}
} else {
t.Errorf("No testing for this case yet : %q", errs)
}

// Timeout, after 3 attempt valid
resp, _, errs = New().Get(ts.URL+casetimeout_after_3_attempt_return_valid).
Timeout(1*time.Second).
Retry(3, 1*time.Second).
End()
if errs != nil {
t.Errorf("No testing for this case yet : %v", errs)
} else {
if resp.StatusCode != 200 {
t.Errorf("Expected [%d] but was [%d]", resp.StatusCode, http.StatusOK)
}
}
}

// testing for Options method
Expand Down