@@ -69,11 +69,21 @@ var (
69
69
// scheme specified in the URL is invalid. This error isn't typed
70
70
// specifically so we resort to matching on the error string.
71
71
schemeErrorRe = regexp .MustCompile (`unsupported protocol scheme` )
72
+
73
+ // A regular expression to match the error returned by net/http when the
74
+ // TLS certificate is not trusted. This error isn't typed
75
+ // specifically so we resort to matching on the error string.
76
+ notTrustedErrorRe = regexp .MustCompile (`certificate is not trusted` )
72
77
)
73
78
74
79
// ReaderFunc is the type of function that can be given natively to NewRequest
75
80
type ReaderFunc func () (io.Reader , error )
76
81
82
+ // ResponseHandlerFunc is a type of function that takes in a Response, and does something with it.
83
+ // It only runs if the initial part of the request was successful.
84
+ // If an error is returned, the client's retry policy will be used to determine whether to retry the whole request.
85
+ type ResponseHandlerFunc func (* http.Response ) error
86
+
77
87
// LenReader is an interface implemented by many in-memory io.Reader's. Used
78
88
// for automatically sending the right Content-Length header when possible.
79
89
type LenReader interface {
@@ -86,6 +96,8 @@ type Request struct {
86
96
// used to rewind the request data in between retries.
87
97
body ReaderFunc
88
98
99
+ responseHandler ResponseHandlerFunc
100
+
89
101
// Embed an HTTP request directly. This makes a *Request act exactly
90
102
// like an *http.Request so that all meta methods are supported.
91
103
* http.Request
@@ -95,11 +107,17 @@ type Request struct {
95
107
// with its context changed to ctx. The provided ctx must be non-nil.
96
108
func (r * Request ) WithContext (ctx context.Context ) * Request {
97
109
return & Request {
98
- body : r .body ,
99
- Request : r .Request .WithContext (ctx ),
110
+ body : r .body ,
111
+ responseHandler : r .responseHandler ,
112
+ Request : r .Request .WithContext (ctx ),
100
113
}
101
114
}
102
115
116
+ // SetResponseHandler allows setting the response handler.
117
+ func (r * Request ) SetResponseHandler (fn ResponseHandlerFunc ) {
118
+ r .responseHandler = fn
119
+ }
120
+
103
121
// BodyBytes allows accessing the request body. It is an analogue to
104
122
// http.Request's Body variable, but it returns a copy of the underlying data
105
123
// rather than consuming it.
@@ -254,7 +272,7 @@ func FromRequest(r *http.Request) (*Request, error) {
254
272
return nil , err
255
273
}
256
274
// Could assert contentLength == r.ContentLength
257
- return & Request {bodyReader , r }, nil
275
+ return & Request {body : bodyReader , Request : r }, nil
258
276
}
259
277
260
278
// NewRequest creates a new wrapped request.
@@ -278,7 +296,7 @@ func NewRequestWithContext(ctx context.Context, method, url string, rawBody inte
278
296
}
279
297
httpReq .ContentLength = contentLength
280
298
281
- return & Request {bodyReader , httpReq }, nil
299
+ return & Request {body : bodyReader , Request : httpReq }, nil
282
300
}
283
301
284
302
// Logger interface allows to use other loggers than
@@ -445,6 +463,9 @@ func baseRetryPolicy(resp *http.Response, err error) (bool, error) {
445
463
}
446
464
447
465
// Don't retry if the error was due to TLS cert verification failure.
466
+ if notTrustedErrorRe .MatchString (v .Error ()) {
467
+ return false , v
468
+ }
448
469
if _ , ok := v .Err .(x509.UnknownAuthorityError ); ok {
449
470
return false , v
450
471
}
@@ -565,9 +586,10 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
565
586
var resp * http.Response
566
587
var attempt int
567
588
var shouldRetry bool
568
- var doErr , checkErr error
589
+ var doErr , respErr , checkErr error
569
590
570
591
for i := 0 ; ; i ++ {
592
+ doErr , respErr = nil , nil
571
593
attempt ++
572
594
573
595
// Always rewind the request body when non-nil.
@@ -600,13 +622,21 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
600
622
601
623
// Check if we should continue with retries.
602
624
shouldRetry , checkErr = c .CheckRetry (req .Context (), resp , doErr )
625
+ if ! shouldRetry && doErr == nil && req .responseHandler != nil {
626
+ respErr = req .responseHandler (resp )
627
+ shouldRetry , checkErr = c .CheckRetry (req .Context (), resp , respErr )
628
+ }
603
629
604
- if doErr != nil {
630
+ err := doErr
631
+ if respErr != nil {
632
+ err = respErr
633
+ }
634
+ if err != nil {
605
635
switch v := logger .(type ) {
606
636
case LeveledLogger :
607
- v .Error ("request failed" , "error" , doErr , "method" , req .Method , "url" , req .URL )
637
+ v .Error ("request failed" , "error" , err , "method" , req .Method , "url" , req .URL )
608
638
case Logger :
609
- v .Printf ("[ERR] %s %s request failed: %v" , req .Method , req .URL , doErr )
639
+ v .Printf ("[ERR] %s %s request failed: %v" , req .Method , req .URL , err )
610
640
}
611
641
} else {
612
642
// Call this here to maintain the behavior of logging all requests,
@@ -669,15 +699,19 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
669
699
}
670
700
671
701
// this is the closest we have to success criteria
672
- if doErr == nil && checkErr == nil && ! shouldRetry {
702
+ if doErr == nil && respErr == nil && checkErr == nil && ! shouldRetry {
673
703
return resp , nil
674
704
}
675
705
676
706
defer c .HTTPClient .CloseIdleConnections ()
677
707
678
- err := doErr
708
+ var err error
679
709
if checkErr != nil {
680
710
err = checkErr
711
+ } else if respErr != nil {
712
+ err = respErr
713
+ } else {
714
+ err = doErr
681
715
}
682
716
683
717
if c .ErrorHandler != nil {
0 commit comments