From a4739f4f5c51917ec36a0fa3829a39d1c27beaf6 Mon Sep 17 00:00:00 2001 From: Cory Cooper Date: Sat, 24 Sep 2022 23:35:32 -0700 Subject: [PATCH] caddyconfig: dependency-free http config loading retries --- caddyconfig/httploader.go | 43 +++++++++++++++++++++++---------------- go.mod | 2 -- go.sum | 3 --- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/caddyconfig/httploader.go b/caddyconfig/httploader.go index 7de9a8bad197..5e79a9ef9844 100644 --- a/caddyconfig/httploader.go +++ b/caddyconfig/httploader.go @@ -15,17 +15,16 @@ package caddyconfig import ( - "context" "crypto/tls" "crypto/x509" "fmt" "io" + "math" "net/http" "os" "time" "github.com/caddyserver/caddy/v2" - "github.com/hashicorp/go-retryablehttp" ) func init() { @@ -47,7 +46,8 @@ type HTTPLoader struct { // Maximum time allowed for a complete connection and request. Timeout caddy.Duration `json:"timeout,omitempty"` - // Maximum number of retries for a successful call to URL. Defaults to 0. + // Maximum number of retries for a successful call to URL. + // Does exponential back-offs (2...4...8...16... seconds) between retries. Defaults to 0. MaxRetries int `json:"max_retries,omitempty"` TLS *struct { @@ -99,7 +99,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) { } } - resp, err := client.Do(req) + resp, err := doHttpCallWithRetries(client, req, hl.MaxRetries) if err != nil { return nil, err } @@ -124,25 +124,34 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) { return result, nil } -func getRetryClient(ctx caddy.Context, maxRetries int, timeout caddy.Duration) (*http.Client, error) { - retryClient := retryablehttp.NewClient() - retryClient.RetryMax = maxRetries - - retryClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) { - if err != nil || (resp.StatusCode < 200 || resp.StatusCode > 499) { - return true, err +// Reattempts the http call using exponential back off when maxRetries is greater than 0. +func doHttpCallWithRetries(client *http.Client, request *http.Request, maxRetries int) (*http.Response, error) { + var resp *http.Response + var err error + + for retry := 0; retry <= maxRetries; retry++ { + if retry > 0 { + caddy.Log().Error(err.Error()) + exponentialBackOff := math.Pow(2, float64(retry)) + caddy.Log().Sugar().Infof("reattempting http load in %g seconds (%v retries remain)", exponentialBackOff, maxRetries-retry) + time.Sleep(time.Duration(exponentialBackOff) * time.Second) + } + resp, err = client.Do(request) + if err != nil { + err = fmt.Errorf("problem calling http loader url: %v", err) + } else if resp.StatusCode < 200 || resp.StatusCode > 499 { + err = fmt.Errorf("bad response status code from http loader url: %v", resp.StatusCode) + } else { + return resp, nil } - return false, err } - retryClient.HTTPClient.Timeout = time.Duration(timeout) - return retryClient.StandardClient(), nil + return resp, err } func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) { - client, err := getRetryClient(ctx, hl.MaxRetries, hl.Timeout) - if err != nil { - return nil, err + client := &http.Client{ + Timeout: time.Duration(hl.Timeout), } if hl.TLS != nil { diff --git a/go.mod b/go.mod index 21b83afcf699..2b558310e2af 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,6 @@ require ( require ( github.com/golang/mock v1.6.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -74,7 +73,6 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/hashicorp/go-retryablehttp v0.7.1 github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index 46cc78c12414..1044ece3e808 100644 --- a/go.sum +++ b/go.sum @@ -398,7 +398,6 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= @@ -412,8 +411,6 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=