Skip to content

Commit 5ed1935

Browse files
authored
Merge pull request #499 from bao-io/beforeRetry
onBeforeRequest 报错时支持走重试逻辑
2 parents 7ba3972 + 468f407 commit 5ed1935

2 files changed

Lines changed: 120 additions & 33 deletions

File tree

request.go

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,60 @@ func (r *Request) Do(ctx ...context.Context) *Response {
663663
return resp
664664
}
665665

666+
func (r *Request) shouldRetry(resp *Response, err error) bool {
667+
if errors.Is(err, context.Canceled) || r.retryOption == nil ||
668+
(r.RetryAttempt >= r.retryOption.MaxRetries && r.retryOption.MaxRetries >= 0) {
669+
return false
670+
}
671+
needRetry := err != nil
672+
if l := len(r.retryOption.RetryConditions); l > 0 {
673+
for i := l - 1; i >= 0; i-- {
674+
needRetry = r.retryOption.RetryConditions[i](resp, err)
675+
if needRetry {
676+
break
677+
}
678+
}
679+
}
680+
return needRetry
681+
}
682+
683+
func (r *Request) prepareRetry(resp *Response, err error) {
684+
r.RetryAttempt++
685+
if l := len(r.retryOption.RetryHooks); l > 0 {
686+
for i := l - 1; i >= 0; i-- {
687+
r.retryOption.RetryHooks[i](resp, err)
688+
}
689+
}
690+
time.Sleep(r.retryOption.GetRetryInterval(resp, r.RetryAttempt))
691+
692+
if r.dumpBuffer != nil {
693+
r.dumpBuffer.Reset()
694+
}
695+
if r.trace != nil {
696+
r.trace = &clientTrace{}
697+
}
698+
if resp != nil {
699+
resp.body = nil
700+
resp.result = nil
701+
resp.error = nil
702+
}
703+
}
704+
705+
// tryRetry attempts retry after an error. Returns true if the request loop should continue.
706+
func (r *Request) tryRetry(resp **Response, err error) bool {
707+
if *resp == nil {
708+
*resp = &Response{Request: r}
709+
}
710+
if err != nil {
711+
(*resp).Err = err
712+
}
713+
if !r.shouldRetry(*resp, err) {
714+
return false
715+
}
716+
r.prepareRetry(*resp, err)
717+
return true
718+
}
719+
666720
func (r *Request) do() (resp *Response, err error) {
667721
defer func() {
668722
if resp == nil {
@@ -673,12 +727,16 @@ func (r *Request) do() (resp *Response, err error) {
673727
}
674728
}()
675729

730+
retry:
676731
for {
677732
if r.Headers == nil {
678733
r.Headers = make(http.Header)
679734
}
680735
for _, f := range r.client.udBeforeRequest {
681736
if err = f(r.client, r); err != nil {
737+
if r.tryRetry(&resp, err) {
738+
continue retry
739+
}
682740
return
683741
}
684742
}
@@ -704,43 +762,12 @@ func (r *Request) do() (resp *Response, err error) {
704762
}
705763
}
706764

707-
if contextCanceled || r.retryOption == nil || (r.RetryAttempt >= r.retryOption.MaxRetries && r.retryOption.MaxRetries >= 0) { // absolutely cannot retry.
765+
if contextCanceled {
708766
return
709767
}
710-
711-
// check retry whether is needed.
712-
needRetry := err != nil // default behaviour: retry if error occurs
713-
if l := len(r.retryOption.RetryConditions); l > 0 { // override default behaviour if custom RetryConditions has been set.
714-
for i := l - 1; i >= 0; i-- {
715-
needRetry = r.retryOption.RetryConditions[i](resp, err)
716-
if needRetry {
717-
break
718-
}
719-
}
720-
}
721-
if !needRetry { // no retry is needed.
768+
if !r.tryRetry(&resp, err) {
722769
return
723770
}
724-
725-
// need retry, attempt to retry
726-
r.RetryAttempt++
727-
if l := len(r.retryOption.RetryHooks); l > 0 {
728-
for i := l - 1; i >= 0; i-- { // run retry hooks in reverse order
729-
r.retryOption.RetryHooks[i](resp, err)
730-
}
731-
}
732-
time.Sleep(r.retryOption.GetRetryInterval(resp, r.RetryAttempt))
733-
734-
// clean up before retry
735-
if r.dumpBuffer != nil {
736-
r.dumpBuffer.Reset()
737-
}
738-
if r.trace != nil {
739-
r.trace = &clientTrace{}
740-
}
741-
resp.body = nil
742-
resp.result = nil
743-
resp.error = nil
744771
}
745772
}
746773

retry_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package req
22

33
import (
44
"bytes"
5+
"errors"
56
"io"
67
"math"
78
"net/http"
@@ -161,6 +162,65 @@ func TestRetryWithModify(t *testing.T) {
161162
tests.AssertEqual(t, 2, resp.Request.RetryAttempt)
162163
}
163164

165+
func TestRetryOnBeforeRequestError(t *testing.T) {
166+
failCount := 0
167+
retryHookCount := 0
168+
c := tc().OnBeforeRequest(func(client *Client, request *Request) error {
169+
failCount++
170+
if failCount < 3 {
171+
return errors.New("temporary before-request error")
172+
}
173+
return nil
174+
})
175+
resp, err := c.R().
176+
SetRetryCount(2).
177+
SetRetryFixedInterval(1 * time.Millisecond).
178+
SetRetryCondition(func(resp *Response, err error) bool {
179+
return err != nil && err.Error() == "temporary before-request error"
180+
}).
181+
SetRetryHook(func(resp *Response, err error) {
182+
retryHookCount++
183+
}).
184+
Get("/")
185+
assertSuccess(t, resp, err)
186+
tests.AssertEqual(t, 2, resp.Request.RetryAttempt)
187+
tests.AssertEqual(t, 2, retryHookCount)
188+
tests.AssertEqual(t, 3, failCount)
189+
}
190+
191+
func TestRetryConditionHasRequestOnBeforeRequestError(t *testing.T) {
192+
c := tc().OnBeforeRequest(func(client *Client, request *Request) error {
193+
return errors.New("before-request error")
194+
})
195+
resp, err := c.R().
196+
SetRetryCount(0).
197+
SetRetryCondition(func(resp *Response, err error) bool {
198+
tests.AssertNotNil(t, resp)
199+
tests.AssertNotNil(t, resp.Request)
200+
tests.AssertEqual(t, "/header", resp.Request.RawURL)
201+
tests.AssertEqual(t, http.MethodGet, resp.Request.Method)
202+
tests.AssertEqual(t, err, resp.Err)
203+
return false
204+
}).
205+
Get("/header")
206+
tests.AssertNotNil(t, err)
207+
tests.AssertNotNil(t, resp.Request)
208+
}
209+
210+
func TestNoRetryOnBeforeRequestErrorWhenConditionFalse(t *testing.T) {
211+
c := tc().OnBeforeRequest(func(client *Client, request *Request) error {
212+
return errors.New("not retryable")
213+
})
214+
resp, err := c.R().
215+
SetRetryCount(3).
216+
SetRetryCondition(func(resp *Response, err error) bool {
217+
return false
218+
}).
219+
Get("/")
220+
tests.AssertNotNil(t, err)
221+
tests.AssertEqual(t, 0, resp.Request.RetryAttempt)
222+
}
223+
164224
func TestRetryFalse(t *testing.T) {
165225
resp, err := tc().SetTimeout(2 * time.Second).R().
166226
SetRetryCount(1).

0 commit comments

Comments
 (0)