github.com/newrelic/newrelic-client-go@v1.1.0/internal/http/retry.go (about) 1 package http 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/x509" 7 "io" 8 "net/http" 9 "net/url" 10 "regexp" 11 12 "github.com/valyala/fastjson" 13 ) 14 15 var ( 16 // A regular expression to match the error returned by net/http when the 17 // configured number of redirects is exhausted. This error isn't typed 18 // specifically so we resort to matching on the error string. 19 redirectsErrorRe = regexp.MustCompile(`stopped after \d+ redirects\z`) 20 21 // A regular expression to match the error returned by net/http when the 22 // scheme specified in the URL is invalid. This error isn't typed 23 // specifically so we resort to matching on the error string. 24 schemeErrorRe = regexp.MustCompile(`unsupported protocol scheme`) 25 ) 26 27 // RetryPolicy provides a callback for retryablehttp's CheckRetry, which 28 // will retry on connection errors and server errors. 29 func RetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) { 30 // do not retry on context.Canceled or context.DeadlineExceeded 31 if ctx.Err() != nil { 32 return false, ctx.Err() 33 } 34 35 if err != nil { 36 if v, ok := err.(*url.Error); ok { 37 // Don't retry if the error was due to too many redirects. 38 if redirectsErrorRe.MatchString(v.Error()) { 39 return false, nil 40 } 41 42 // Don't retry if the error was due to an invalid protocol scheme. 43 if schemeErrorRe.MatchString(v.Error()) { 44 return false, nil 45 } 46 47 // Don't retry if the error was due to TLS cert verification failure. 48 if _, ok := v.Err.(x509.UnknownAuthorityError); ok { 49 return false, nil 50 } 51 } 52 53 // The error is likely recoverable so retry. 54 return true, nil 55 } 56 57 // Check the response code. We retry on 5xx responses to allow 58 // the server time to recover, as 5xx's are typically not permanent 59 // errors and may relate to outages on the server side. We are notably 60 // disallowing retries for 500 errors here as the underlying APIs use them to 61 // provide useful error validation messages that should be passed back to the 62 // end user. This will catch invalid response codes as well, like 0 and 999. 63 // 429 Too Many Requests is retried as well to handle the aggressive rate limiting 64 // of the Synthetics API. 65 if resp.StatusCode == 0 || 66 resp.StatusCode == 429 || 67 resp.StatusCode == 500 || 68 resp.StatusCode >= 502 { 69 return true, nil 70 } 71 72 // Check for json response and description for retries. 73 // i.e.: when receiving TOO_MANY_REQUESTS it is a HTTP 200 code with an 74 // error status that needed to be retried. 75 json, jErr := io.ReadAll(resp.Body) 76 resp.Body = io.NopCloser(bytes.NewBuffer(json)) 77 if jErr == nil { 78 errorClass := fastjson.GetString(json, "errors", "0", "extensions", "errorClass") 79 if errorClass == ErrClassTooManyRequests.Error() { 80 return true, ErrClassTooManyRequests 81 } 82 } 83 84 return false, nil 85 }