tryAgain takes the time that the handler was initially invoked, the amount of retries already performed, as well as any error currently obtained, and the request being tried, and returns true if another attempt should be made at proxying the request. If true is returned, it has already blocked long
(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request, logger *zap.Logger)
| 1322 | // the next retry (i.e. no more sleeping is needed). If false is |
| 1323 | // returned, the handler should stop trying to proxy the request. |
| 1324 | func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request, logger *zap.Logger) bool { |
| 1325 | // no retries are configured |
| 1326 | if lb.TryDuration == 0 && lb.Retries == 0 { |
| 1327 | return false |
| 1328 | } |
| 1329 | |
| 1330 | // if we've tried long enough, break |
| 1331 | if lb.TryDuration > 0 && time.Since(start) >= time.Duration(lb.TryDuration) { |
| 1332 | return false |
| 1333 | } |
| 1334 | |
| 1335 | // if we've reached the retry limit, break |
| 1336 | if lb.Retries > 0 && retries >= lb.Retries { |
| 1337 | return false |
| 1338 | } |
| 1339 | |
| 1340 | // if the error occurred while dialing (i.e. a connection |
| 1341 | // could not even be established to the upstream), then it |
| 1342 | // should be safe to retry, since without a connection, no |
| 1343 | // HTTP request can be transmitted; but if the error is not |
| 1344 | // specifically a dialer error, we need to be careful |
| 1345 | if proxyErr != nil { |
| 1346 | _, isDialError := proxyErr.(DialError) |
| 1347 | _, isRetryableResponse := proxyErr.(retryableResponseError) |
| 1348 | herr, isHandlerError := proxyErr.(caddyhttp.HandlerError) |
| 1349 | |
| 1350 | // if the error occurred after a connection was established, |
| 1351 | // we have to assume the upstream received the request, and |
| 1352 | // retries need to be carefully decided, because some requests |
| 1353 | // are not idempotent; retryableResponseError is excluded here |
| 1354 | // because its retry decision was already made in reverseProxy() |
| 1355 | // when the response matchers were evaluated |
| 1356 | if !isDialError && !isRetryableResponse && (!isHandlerError || !errors.Is(herr, errNoUpstream)) { |
| 1357 | if lb.RetryMatch == nil && req.Method != "GET" { |
| 1358 | // by default, don't retry requests if they aren't GET |
| 1359 | return false |
| 1360 | } |
| 1361 | |
| 1362 | // set transport error flag so CEL expressions can use |
| 1363 | // {rp.is_transport_error} to decide whether to retry |
| 1364 | repl, _ := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) |
| 1365 | if repl != nil { |
| 1366 | repl.Set("http.reverse_proxy.is_transport_error", true) |
| 1367 | defer repl.Delete("http.reverse_proxy.is_transport_error") |
| 1368 | } |
| 1369 | |
| 1370 | match, err := lb.RetryMatch.AnyMatchWithError(req) |
| 1371 | if err != nil { |
| 1372 | logger.Error("error matching request for retry", zap.Error(err)) |
| 1373 | return false |
| 1374 | } |
| 1375 | if !match { |
| 1376 | return false |
| 1377 | } |
| 1378 | } |
| 1379 | } |
| 1380 | |
| 1381 | // fast path; if the interval is zero, we don't need to wait |
no test coverage detected