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  }