github.com/searKing/golang/go@v1.2.117/net/http/backoff.transport.go (about) 1 // Copyright 2022 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package http 6 7 import ( 8 "fmt" 9 "net/http" 10 "time" 11 12 time_ "github.com/searKing/golang/go/time" 13 ) 14 15 // RoundTripperWithBackoff wraps http.RoundTripper retryable by backoff. 16 func RoundTripperWithBackoff(rt http.RoundTripper, opts ...DoWithBackoffOption) http.RoundTripper { 17 return RoundTripFunc(func(req *http.Request) (resp *http.Response, err error) { 18 var opt doWithBackoff 19 opt.SetDefault() 20 if rt == nil { 21 rt = http.DefaultTransport 22 } 23 opt.DoRetryHandler = func(req *http.Request, retry int) (*http.Response, error) { return rt.RoundTrip(req) } 24 25 opt.ApplyOptions(opts...) 26 if opt.RetryAfter == nil { 27 opt.RetryAfter = RetryAfter 28 } 29 opt.Complete() 30 31 var option []time_.ExponentialBackOffOption 32 option = append(option, time_.WithExponentialBackOffOptionMaxElapsedCount(3)) 33 option = append(option, opt.ExponentialBackOffOption...) 34 backoff := time_.NewDefaultExponentialBackOff(option...) 35 rewindableErr := RequestWithBodyRewindable(req) 36 var retries int 37 for { 38 if retries > 0 && req.GetBody != nil { 39 newBody, err := req.GetBody() 40 if err != nil { 41 return nil, err 42 } 43 req.Body = newBody 44 } 45 var do = opt.DoRetryHandler 46 httpDo := do 47 if opt.clientInterceptor != nil { 48 httpDo = func(req *http.Request, retry int) (*http.Response, error) { 49 return opt.clientInterceptor(req, retry, do, opts...) 50 } 51 } 52 resp, err := httpDo(req, retries) 53 54 wait, ok := backoff.NextBackOff() 55 if !ok { 56 if err != nil { 57 return nil, fmt.Errorf("http do reach backoff limit after retries %d: %w", retries, err) 58 } else { 59 return resp, nil 60 } 61 } 62 63 wait, retry := opt.RetryAfter(resp, err, wait) 64 if !retry { 65 if err != nil { 66 return nil, fmt.Errorf("http do reach server limit after retries %d: %w", retries, err) 67 } else { 68 return resp, nil 69 } 70 } 71 72 if rewindableErr != nil { 73 if err != nil { 74 return nil, fmt.Errorf("http do cannot rewindbody after retries %d: %w", retries, err) 75 } else { 76 return resp, nil 77 } 78 } 79 80 timer := time.NewTimer(wait) 81 select { 82 case <-timer.C: 83 retries++ 84 continue 85 case <-req.Context().Done(): 86 timer.Stop() 87 if err != nil { 88 return nil, fmt.Errorf("http do canceled after retries %d: %w", retries, err) 89 } else { 90 return resp, nil 91 } 92 } 93 } 94 }) 95 }