From b19a91ae31f644e6e9c5124a7d362932a615e6ca Mon Sep 17 00:00:00 2001 From: Anthony Hartanto Date: Tue, 7 Aug 2018 18:22:17 +0700 Subject: [PATCH 1/5] add check timeout on retry --- gorequest.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gorequest.go b/gorequest.go index ea1a55c..07dbfc9 100644 --- a/gorequest.go +++ b/gorequest.go @@ -949,7 +949,7 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e if errs != nil { return nil, nil, errs } - if s.isRetryableRequest(resp) { + if s.isRetryableRequest(resp, errs) { resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt)) break } @@ -962,8 +962,8 @@ 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) || IsTimeout(errs)) { time.Sleep(s.Retryable.RetryerTime) s.Retryable.Attempt++ return false @@ -980,6 +980,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() From 314f1e5de9370a847899d06b505c85ba72012890 Mon Sep 17 00:00:00 2001 From: Anthony Hartanto Date: Wed, 8 Aug 2018 11:55:18 +0700 Subject: [PATCH 2/5] add retry on error timeout --- gorequest.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/gorequest.go b/gorequest.go index 07dbfc9..462c455 100644 --- a/gorequest.go +++ b/gorequest.go @@ -947,11 +947,14 @@ 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, errs) { - resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt)) - break + if s.isErrNotRetryableRequest(errs) { + return nil, nil, errs + } + } else { + if s.isRetryableRequest(resp, errs) { + resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt)) + break + } } } @@ -963,9 +966,19 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e } 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) || IsTimeout(errs)) { + 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 From f213b39448fc53a5e19a7baa079b7e5d49c43859 Mon Sep 17 00:00:00 2001 From: Anthony Hartanto Date: Wed, 8 Aug 2018 12:10:04 +0700 Subject: [PATCH 3/5] fix retry attempt bug --- gorequest.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorequest.go b/gorequest.go index 462c455..e1c34bf 100644 --- a/gorequest.go +++ b/gorequest.go @@ -943,7 +943,8 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e resp Response body []byte ) - + + s.Retryable.Attempt = 1 for { resp, body, errs = s.getResponseBytes() if errs != nil { From ed113e01113abcb803f60c7e9c67a2c4d5e8e09c Mon Sep 17 00:00:00 2001 From: Anthony Hartanto Date: Fri, 10 Aug 2018 14:40:29 +0700 Subject: [PATCH 4/5] add err info retry --- gorequest.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorequest.go b/gorequest.go index e1c34bf..635b451 100644 --- a/gorequest.go +++ b/gorequest.go @@ -943,12 +943,12 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e resp Response body []byte ) - - s.Retryable.Attempt = 1 + for { resp, body, errs = s.getResponseBytes() if errs != nil { if s.isErrNotRetryableRequest(errs) { + errs = append(errs, fmt.Errorf("retry attempt: %d", s.Retryable.Attempt)) return nil, nil, errs } } else { From e039e15dd5ed6b51bd9479726aae55a7fbe1ad50 Mon Sep 17 00:00:00 2001 From: Anthony Hartanto Date: Fri, 10 Aug 2018 14:44:22 +0700 Subject: [PATCH 5/5] add unit test for retry on timeout --- gorequest_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/gorequest_test.go b/gorequest_test.go index 2cf5a74..83df44d 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -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 @@ -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++ } })) @@ -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