github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/plugin/retry/middleware.go (about)

     1  package retry
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"time"
     8  
     9  	"github.com/Knetic/govaluate"
    10  	"github.com/felixge/httpsnoop"
    11  	"github.com/rafaeljesus/retry-go"
    12  	log "github.com/sirupsen/logrus"
    13  
    14  	janusErr "github.com/hellofresh/janus/pkg/errors"
    15  	"github.com/hellofresh/janus/pkg/metrics"
    16  )
    17  
    18  const (
    19  	defaultPredicate = "statusCode == 0 || statusCode >= 500"
    20  	proxySection     = "proxy"
    21  )
    22  
    23  // NewRetryMiddleware creates a new retry middleware
    24  func NewRetryMiddleware(cfg Config) func(http.Handler) http.Handler {
    25  	return func(handler http.Handler) http.Handler {
    26  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    27  			log.WithFields(log.Fields{
    28  				"attempts": cfg.Attempts,
    29  				"backoff":  cfg.Backoff,
    30  			}).Debug("Starting retry middleware")
    31  
    32  			if cfg.Predicate == "" {
    33  				cfg.Predicate = defaultPredicate
    34  			}
    35  
    36  			expression, err := govaluate.NewEvaluableExpression(cfg.Predicate)
    37  			if err != nil {
    38  				log.WithError(err).Error("could not create an expression with this predicate")
    39  				handler.ServeHTTP(w, r)
    40  				return
    41  			}
    42  
    43  			if err := retry.Do(func() error {
    44  				m := httpsnoop.CaptureMetrics(handler, w, r)
    45  
    46  				params := make(map[string]interface{}, 8)
    47  				params["statusCode"] = m.Code
    48  				params["request"] = r
    49  
    50  				result, err := expression.Evaluate(params)
    51  				if err != nil {
    52  					return errors.New("cannot evaluate the expression")
    53  				}
    54  
    55  				if result.(bool) {
    56  					return fmt.Errorf("%s %s request failed", r.Method, r.URL)
    57  				}
    58  
    59  				return nil
    60  			}, cfg.Attempts, time.Duration(cfg.Backoff)); err != nil {
    61  				statsClient := metrics.WithContext(r.Context())
    62  				statsClient.SetHTTPRequestSection(proxySection).TrackRequest(r, nil, false).ResetHTTPRequestSection()
    63  				janusErr.Handler(w, r, fmt.Errorf("request failed too many times: %w", err))
    64  			}
    65  		})
    66  	}
    67  }