github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/retrier/retrier.go (about)

     1  package retrier
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"os"
    11  	"strings"
    12  	"syscall"
    13  
    14  	clientv1 "github.com/qiniu/go-sdk/v7/client"
    15  	"github.com/qiniu/go-sdk/v7/storagev2/backoff"
    16  )
    17  
    18  type (
    19  	// RetryDecision 重试决策
    20  	RetryDecision int
    21  	// RetrierOptions 重试器选项
    22  	RetrierOptions backoff.BackoffOptions
    23  
    24  	// Retrier 重试器接口
    25  	Retrier interface {
    26  		// Retry 判断是否重试,如何重试
    27  		Retry(*http.Response, error, *RetrierOptions) RetryDecision
    28  	}
    29  
    30  	neverRetrier      struct{}
    31  	errorRetrier      struct{}
    32  	customizedRetrier struct {
    33  		retryFn func(*http.Response, error, *RetrierOptions) RetryDecision
    34  	}
    35  )
    36  
    37  const (
    38  	// 不再重试
    39  	DontRetry RetryDecision = iota
    40  
    41  	// 重试下一个域名
    42  	TryNextHost
    43  
    44  	// 重试当前域名
    45  	RetryRequest
    46  )
    47  
    48  // NewRetrier 创建自定义重试器
    49  func NewRetrier(fn func(*http.Response, error, *RetrierOptions) RetryDecision) Retrier {
    50  	return customizedRetrier{retryFn: fn}
    51  }
    52  
    53  func (retrier customizedRetrier) Retry(response *http.Response, err error, options *RetrierOptions) RetryDecision {
    54  	return retrier.retryFn(response, err, options)
    55  }
    56  
    57  // NewNeverRetrier 创建从不重试的重试器
    58  func NewNeverRetrier() Retrier {
    59  	return neverRetrier{}
    60  }
    61  
    62  func (neverRetrier) Retry(*http.Response, error, *RetrierOptions) RetryDecision {
    63  	return DontRetry
    64  }
    65  
    66  // NewErrorRetrier 创建错误重试器,为七牛默认的错误重试器
    67  func NewErrorRetrier() Retrier {
    68  	return errorRetrier{}
    69  }
    70  
    71  func (errorRetrier) Retry(response *http.Response, err error, _ *RetrierOptions) RetryDecision {
    72  	if isResponseRetryable(response) {
    73  		return RetryRequest
    74  	} else if err == nil {
    75  		return DontRetry
    76  	} else {
    77  		return getRetryDecisionForError(err)
    78  	}
    79  }
    80  
    81  func isResponseRetryable(resp *http.Response) bool {
    82  	if resp == nil {
    83  		return false
    84  	}
    85  	return IsStatusCodeRetryable(resp.StatusCode)
    86  }
    87  
    88  func IsStatusCodeRetryable(statusCode int) bool {
    89  	if statusCode < 500 {
    90  		return false
    91  	}
    92  
    93  	if statusCode == 501 || statusCode == 509 || statusCode == 579 ||
    94  		statusCode == 608 || statusCode == 612 || statusCode == 614 || statusCode == 616 || statusCode == 618 ||
    95  		statusCode == 630 || statusCode == 631 || statusCode == 632 || statusCode == 640 || statusCode == 701 {
    96  		return false
    97  	}
    98  
    99  	return true
   100  }
   101  
   102  func IsErrorRetryable(err error) bool {
   103  	if err == nil {
   104  		return false
   105  	}
   106  
   107  	switch getRetryDecisionForError(err) {
   108  	case RetryRequest, TryNextHost:
   109  		return true
   110  	default:
   111  		return false
   112  	}
   113  }
   114  
   115  var ErrMaliciousResponse = errors.New("malicious response")
   116  
   117  func getRetryDecisionForError(err error) RetryDecision {
   118  	if err == nil {
   119  		return DontRetry
   120  	}
   121  
   122  	tryToUnwrapUnderlyingError := func(err error) (error, bool) {
   123  		switch err := err.(type) {
   124  		case *os.PathError:
   125  			return err.Err, true
   126  		case *os.LinkError:
   127  			return err.Err, true
   128  		case *os.SyscallError:
   129  			return err.Err, true
   130  		case *url.Error:
   131  			return err.Err, true
   132  		case *net.OpError:
   133  			return err.Err, true
   134  		}
   135  		return err, false
   136  	}
   137  	unwrapUnderlyingError := func(err error) error {
   138  		ok := true
   139  		for ok {
   140  			err, ok = tryToUnwrapUnderlyingError(err)
   141  		}
   142  		return err
   143  	}
   144  
   145  	unwrapedErr := unwrapUnderlyingError(err)
   146  	if unwrapedErr == context.DeadlineExceeded {
   147  		return DontRetry
   148  	} else if unwrapedErr == ErrMaliciousResponse {
   149  		return RetryRequest
   150  	} else if os.IsTimeout(unwrapedErr) {
   151  		return RetryRequest
   152  	} else if unwrapedErr == io.ErrUnexpectedEOF {
   153  		return RetryRequest
   154  	} else if dnsError, ok := unwrapedErr.(*net.DNSError); ok && isDnsNotFoundError(dnsError) {
   155  		return TryNextHost
   156  	} else if syscallError, ok := unwrapedErr.(*os.SyscallError); ok {
   157  		switch syscallError.Err {
   158  		case syscall.ECONNREFUSED, syscall.ECONNABORTED, syscall.ECONNRESET:
   159  			return TryNextHost
   160  		default:
   161  			return DontRetry
   162  		}
   163  	} else if errno, ok := unwrapedErr.(syscall.Errno); ok {
   164  		switch errno {
   165  		case syscall.ECONNREFUSED, syscall.ECONNABORTED, syscall.ECONNRESET:
   166  			return TryNextHost
   167  		default:
   168  			return DontRetry
   169  		}
   170  	} else if unwrapedErr == context.Canceled {
   171  		return DontRetry
   172  	} else if clientErr, ok := unwrapedErr.(*clientv1.ErrorInfo); ok {
   173  		if clientErr.Code == http.StatusBadRequest && strings.Contains(unwrapedErr.Error(), "transfer acceleration is not configured on this bucket") {
   174  			return TryNextHost
   175  		} else if IsStatusCodeRetryable(clientErr.Code) {
   176  			return RetryRequest
   177  		} else {
   178  			return DontRetry
   179  		}
   180  	}
   181  	desc := unwrapedErr.Error()
   182  	if strings.Contains(desc, "use of closed network connection") ||
   183  		strings.Contains(desc, "unexpected EOF reading trailer") ||
   184  		strings.Contains(desc, "transport connection broken") ||
   185  		strings.Contains(desc, "server closed idle connection") {
   186  		return RetryRequest
   187  	}
   188  	return DontRetry
   189  }