I believe that clean and elegant retry code is notoriously difficult, so I'm wondering how the following code could be improved. The retryGet
function should have the same interface and contract as http.Get
, except for the number of attempts argument, of course.
func retryGet(url string, maxAttempts int) (*http.Response, error) {
attempts := 0
if maxAttempts < 1 {
return nil, errors.New("maxAttempts must be at least 1")
}
for {
attempts++
response, err := http.Get(url)
if err == nil && response.StatusCode == http.StatusOK {
return response, nil
}
delay, retry := shouldRetry(maxAttempts, attempts, response)
if !retry {
return response, err
}
if err == nil {
defer response.Body.Close()
}
time.Sleep(delay)
}
}
func shouldRetry(maxAttempts, attempts int, response *http.Response) (time.Duration, bool) {
if attempts >= maxAttempts {
return time.Duration(0), false
}
delay := time.Duration(attempts) * time.Second
if response != nil && response.Header.Get("Retry-After") != "" {
after, err := strconv.ParseInt(response.Header.Get("Retry-After"), 10, 64)
if err != nil && after > 0 {
delay = time.Duration(after) * time.Second
}
}
return delay, true
}