diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCAndScanCentralUnirestHelper.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCAndScanCentralUnirestHelper.java index 38b099aeb7..034f4ecde6 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCAndScanCentralUnirestHelper.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCAndScanCentralUnirestHelper.java @@ -22,9 +22,12 @@ import com.fortify.cli.ssc._common.session.helper.SSCAndScanCentralSessionDescriptor; import kong.unirest.UnirestInstance; +import kong.unirest.apache.ApacheClient; public class SSCAndScanCentralUnirestHelper { public static final void configureSscUnirestInstance(UnirestInstance unirest, SSCAndScanCentralSessionDescriptor sessionDescriptor) { + unirest.config().httpClient(config -> new ApacheClient(config, cb -> + cb.setServiceUnavailableRetryStrategy(new SSCRetryStrategy()))); UnirestUnexpectedHttpResponseConfigurer.configure(unirest); UnirestJsonHeaderConfigurer.configure(unirest); UnirestUrlConfigConfigurer.configure(unirest, sessionDescriptor.getSscUrlConfig()); diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCRetryStrategy.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCRetryStrategy.java new file mode 100644 index 0000000000..57f21e6099 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/helper/SSCRetryStrategy.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.ssc._common.rest.helper; + +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ServiceUnavailableRetryStrategy; +import org.apache.http.protocol.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements an Apache HttpClient 4.x {@link ServiceUnavailableRetryStrategy} + * that will retry a request if the server responds with an HTTP 502 (Bad Gateway) + * or 503 (Service Unavailable) response, using exponential backoff with jitter. + */ +public final class SSCRetryStrategy implements ServiceUnavailableRetryStrategy { + private static final Logger LOG = LoggerFactory.getLogger(SSCRetryStrategy.class); + private static final int MAX_RETRIES = 3; + private static final long BASE_DELAY_MS = 1000; + private static final long MAX_JITTER_MS = 500; + private final ThreadLocal interval = new ThreadLocal(); + + public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) { + int statusCode = response.getStatusLine().getStatusCode(); + if ( executionCount <= MAX_RETRIES && (statusCode == 502 || statusCode == 503) ) { + long delay = BASE_DELAY_MS * (1L << (executionCount - 1)); + long jitter = ThreadLocalRandom.current().nextLong(MAX_JITTER_MS + 1); + long totalDelay = delay + jitter; + LOG.debug("SSC returned {}; retrying (attempt {}/{}) after {} ms", statusCode, executionCount, MAX_RETRIES, totalDelay); + interval.set(totalDelay); + return true; + } + return false; + } + + public long getRetryInterval() { + Long result = interval.get(); + return result == null ? -1 : result; + } +}