001 002package io.vrap.rmf.base.client.http; 003 004import java.time.temporal.ChronoUnit; 005import java.util.List; 006import java.util.Optional; 007import java.util.concurrent.CompletableFuture; 008import java.util.concurrent.ExecutorService; 009import java.util.concurrent.ScheduledExecutorService; 010import java.util.function.Function; 011 012import io.vrap.rmf.base.client.*; 013import io.vrap.rmf.base.client.utils.json.JsonException; 014import io.vrap.rmf.base.client.utils.json.JsonUtils; 015 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019import dev.failsafe.Failsafe; 020import dev.failsafe.FailsafeExecutor; 021import dev.failsafe.RetryPolicy; 022import dev.failsafe.event.ExecutionAttemptedEvent; 023import dev.failsafe.spi.Scheduler; 024 025/** 026 * Implementation for a retry of a requests upon configured response status codes 027 */ 028public class RetryMiddleware implements RetryRequestMiddleware, AutoCloseable { 029 static final String loggerName = ClientBuilder.COMMERCETOOLS + ".retry"; 030 031 /** 032 * @deprecated use {@link RetryRequestMiddleware#DEFAULT_MAX_DELAY} instead 033 */ 034 @Deprecated 035 public static final int DEFAULT_MAX_DELAY = RetryRequestMiddleware.DEFAULT_MAX_DELAY; 036 /** 037 * @deprecated use {@link RetryRequestMiddleware#DEFAULT_INITIAL_DELAY} instead 038 */ 039 @Deprecated 040 public static final int DEFAULT_INITIAL_DELAY = RetryRequestMiddleware.DEFAULT_INITIAL_DELAY; 041 /** 042 * @deprecated use {@link RetryRequestMiddleware#DEFAULT_RETRY_STATUS_CODES} instead 043 */ 044 @Deprecated 045 public static final List<Integer> DEFAULT_RETRY_STATUS_CODES = RetryRequestMiddleware.DEFAULT_RETRY_STATUS_CODES; 046 private static final InternalLogger logger = InternalLogger.getLogger(loggerName); 047 private static final Logger classLogger = LoggerFactory.getLogger(RetryMiddleware.class); 048 049 private final FailsafeExecutor<ApiHttpResponse<byte[]>> failsafeExecutor; 050 051 /** 052 * @deprecated use {@link RetryRequestMiddleware#of(int)} instead 053 * @param maxRetries number of retries before giving up 054 */ 055 @Deprecated 056 public RetryMiddleware(final int maxRetries) { 057 this(Scheduler.DEFAULT, maxRetries, RetryRequestMiddleware.DEFAULT_INITIAL_DELAY, 058 RetryRequestMiddleware.DEFAULT_MAX_DELAY, RetryRequestMiddleware.DEFAULT_RETRY_STATUS_CODES, null); 059 } 060 061 /** 062 * @deprecated use {@link RetryRequestMiddleware#of(int, List)} instead 063 * @param maxRetries number of retries before giving up 064 * @param statusCodes response status codes to be retried 065 */ 066 @Deprecated 067 public RetryMiddleware(final int maxRetries, final List<Integer> statusCodes) { 068 this(Scheduler.DEFAULT, maxRetries, RetryRequestMiddleware.DEFAULT_INITIAL_DELAY, 069 RetryRequestMiddleware.DEFAULT_MAX_DELAY, statusCodes, null); 070 } 071 072 RetryMiddleware(final int maxRetries, final List<Integer> statusCodes, 073 final List<Class<? extends Throwable>> failures) { 074 this(Scheduler.DEFAULT, maxRetries, RetryRequestMiddleware.DEFAULT_INITIAL_DELAY, 075 RetryRequestMiddleware.DEFAULT_MAX_DELAY, statusCodes, failures); 076 } 077 078 /** 079 * @deprecated use {@link RetryRequestMiddleware#of(int, long, long)} instead 080 * @param maxRetries number of retries before giving up 081 * @param delay initial delay before retry 082 * @param maxDelay maximum delay before retry 083 */ 084 @Deprecated 085 public RetryMiddleware(final int maxRetries, final long delay, final long maxDelay) { 086 this(Scheduler.DEFAULT, maxRetries, delay, maxDelay, RetryRequestMiddleware.DEFAULT_RETRY_STATUS_CODES, null); 087 } 088 089 /** 090 * @deprecated use {@link RetryRequestMiddleware#of(int, long, long, List)} instead 091 * @param maxRetries number of retries before giving up 092 * @param delay initial delay before retry 093 * @param maxDelay maximum delay before retry 094 * @param statusCodes response status codes to be retried 095 */ 096 @Deprecated 097 public RetryMiddleware(final int maxRetries, final long delay, final long maxDelay, 098 final List<Integer> statusCodes) { 099 this(Scheduler.DEFAULT, maxRetries, delay, maxDelay, statusCodes, null); 100 } 101 102 RetryMiddleware(final int maxRetries, final long delay, final long maxDelay, final List<Integer> statusCodes, 103 final List<Class<? extends Throwable>> failures) { 104 this(Scheduler.DEFAULT, maxRetries, delay, maxDelay, RetryRequestMiddleware.handleFailures(failures) 105 .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes))); 106 } 107 108 RetryMiddleware(final ExecutorService executorService, final int maxRetries, final long delay, final long maxDelay, 109 final List<Integer> statusCodes, final List<Class<? extends Throwable>> failures) { 110 this(executorService, maxRetries, delay, maxDelay, RetryRequestMiddleware.handleFailures(failures) 111 .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes))); 112 } 113 114 RetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries, final long delay, 115 final long maxDelay, final List<Integer> statusCodes, final List<Class<? extends Throwable>> failures) { 116 this(executorService, maxRetries, delay, maxDelay, RetryRequestMiddleware.handleFailures(failures) 117 .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes))); 118 } 119 120 RetryMiddleware(final Scheduler scheduler, final int maxRetries, final long delay, final long maxDelay, 121 final List<Integer> statusCodes, final List<Class<? extends Throwable>> failures) { 122 this(scheduler, maxRetries, delay, maxDelay, RetryRequestMiddleware.handleFailures(failures) 123 .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes))); 124 } 125 126 RetryMiddleware(final int maxRetries, final long delay, final long maxDelay, 127 final FailsafeRetryPolicyBuilderOptions fn) { 128 this(Scheduler.DEFAULT, maxRetries, delay, maxDelay, fn); 129 } 130 131 RetryMiddleware(final ExecutorService executorService, final int maxRetries, final long delay, final long maxDelay, 132 final FailsafeRetryPolicyBuilderOptions fn) { 133 this(Scheduler.of(executorService), maxRetries, delay, maxDelay, fn); 134 } 135 136 RetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries, final long delay, 137 final long maxDelay, final FailsafeRetryPolicyBuilderOptions fn) { 138 this(Scheduler.of(executorService), maxRetries, delay, maxDelay, fn); 139 } 140 141 RetryMiddleware(final Scheduler scheduler, final int maxRetries, final long delay, final long maxDelay, 142 final FailsafeRetryPolicyBuilderOptions fn) { 143 RetryPolicy<ApiHttpResponse<byte[]>> retryPolicy = fn 144 .apply(RetryPolicy.<ApiHttpResponse<byte[]>> builder() 145 .withBackoff(delay, maxDelay, ChronoUnit.MILLIS) 146 .withJitter(0.25) 147 .withMaxRetries(maxRetries) 148 .onRetry(this::logEventFailure)) 149 .build(); 150 this.failsafeExecutor = Failsafe.with(retryPolicy).with(scheduler); 151 } 152 153 private void logEventFailure(ExecutionAttemptedEvent<ApiHttpResponse<byte[]>> event) { 154 final int attempt = event.getAttemptCount(); 155 156 logger.info(() -> "Retry #" + attempt); 157 logger.trace(() -> { 158 final Throwable failure = event.getLastException(); 159 if (failure instanceof ApiHttpException) { 160 final ApiHttpException httpException = (ApiHttpException) failure; 161 final ApiHttpRequest request = httpException.getRequest(); 162 final ApiHttpResponse<byte[]> response = httpException.getResponse(); 163 if (request != null) { 164 return requestLog(attempt, request, response); 165 } 166 } 167 return event.toString(); 168 }); 169 } 170 171 private String requestLog(final int attempt, ApiHttpRequest request, ApiHttpResponse<?> response) { 172 String output; 173 final String httpMethodAndUrl = request.getMethod().name() + " " + request.getUrl().toString(); 174 if (request.getBody() != null) { 175 final String unformattedBody = request.getSecuredBody(); 176 final boolean isJsonRequest = request.getHeaders() 177 .getHeaders(ApiHttpHeaders.CONTENT_TYPE) 178 .stream() 179 .findFirst() 180 .map(ct -> ct.getValue().toLowerCase().contains("json")) 181 .orElse(true); 182 if (isJsonRequest) { 183 String prettyPrint; 184 try { 185 prettyPrint = JsonUtils.prettyPrint(unformattedBody); 186 } 187 catch (final JsonException e) { 188 classLogger.warn("pretty print failed", e); 189 prettyPrint = unformattedBody; 190 } 191 output = "Retry #" + attempt + ": " + request + "\n" + httpMethodAndUrl + "\nformatted: " + prettyPrint; 192 } 193 else { 194 output = "Retry #" + attempt + ": " + request + "\n" + request.getMethod().name() + " " 195 + request.getUrl() + " " + unformattedBody; 196 } 197 } 198 else { 199 output = "Retry #" + attempt + ": " + request + "\n" + httpMethodAndUrl + " <no body>"; 200 } 201 if (response != null) { 202 output += "\nFailure response: " + response.getStatusCode() + "\n" + response + "\n" 203 + Optional.ofNullable(response.getBody()) 204 .map(body -> JsonUtils.prettyPrint(response.getBodyAsString().orElse(""))) 205 .orElse("<no body>"); 206 } 207 return output; 208 } 209 210 /** 211 * @deprecated max parallel requests are limited by underlying HTTP client 212 * @param maxRetries number of retries before giving up 213 * @param maxParallelRequests maximum number of parallel retry requests 214 */ 215 @Deprecated 216 public RetryMiddleware(final int maxParallelRequests, final int maxRetries) { 217 this(maxRetries, RetryRequestMiddleware.DEFAULT_INITIAL_DELAY, RetryRequestMiddleware.DEFAULT_MAX_DELAY, 218 RetryRequestMiddleware.DEFAULT_RETRY_STATUS_CODES, null); 219 } 220 221 /** 222 * @deprecated max parallel requests are limited by underlying HTTP client 223 * @param maxRetries number of retries before giving up 224 * @param maxParallelRequests maximum number of parallel retry requests 225 * @param statusCodes response status codes to be retried 226 */ 227 @Deprecated 228 public RetryMiddleware(final int maxParallelRequests, final int maxRetries, final List<Integer> statusCodes) { 229 this(maxRetries, RetryRequestMiddleware.DEFAULT_INITIAL_DELAY, RetryRequestMiddleware.DEFAULT_MAX_DELAY, 230 statusCodes, null); 231 } 232 233 /** 234 * @deprecated max parallel requests are limited by underlying HTTP client 235 * @param maxRetries number of retries before giving up 236 * @param maxParallelRequests maximum number of parallel retry requests 237 * @param delay initial delay before retry 238 * @param maxDelay maximum delay before retry 239 */ 240 @Deprecated 241 public RetryMiddleware(final int maxParallelRequests, final int maxRetries, final long delay, final long maxDelay) { 242 this(maxRetries, delay, maxDelay, RetryRequestMiddleware.DEFAULT_RETRY_STATUS_CODES, null); 243 } 244 245 /** 246 * @deprecated max parallel requests are limited by underlying HTTP client 247 * @param maxRetries number of retries before giving up 248 * @param maxParallelRequests maximum number of parallel retry requests 249 * @param delay initial delay before retry 250 * @param maxDelay maximum delay before retry 251 * @param statusCodes response status codes to be retried 252 */ 253 @Deprecated 254 public RetryMiddleware(final int maxParallelRequests, final int maxRetries, final long delay, final long maxDelay, 255 List<Integer> statusCodes) { 256 this(maxRetries, delay, maxDelay, statusCodes, null); 257 } 258 259 @Override 260 public CompletableFuture<ApiHttpResponse<byte[]>> invoke(final ApiHttpRequest request, 261 final Function<ApiHttpRequest, CompletableFuture<ApiHttpResponse<byte[]>>> next) { 262 return failsafeExecutor.getStageAsync(() -> next.apply(request)); 263 } 264 265 @Override 266 public void close() { 267 } 268}