RetryStrategy.java
/*
* okhttp-client-extensions - A set of helpful extensions to support okhttp clients
* Copyright © 2025 Andy Miles (andy.miles@amilesend.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.amilesend.client.connection.retry;
import com.amilesend.client.connection.RequestException;
import com.amilesend.client.connection.ResponseException;
import com.amilesend.client.connection.ThrottledException;
import com.amilesend.client.util.StringUtils;
import okhttp3.Response;
/**
* Defines the interface for a retry strategy that enables different approaches to determine how and when
* to retry an invocation.
*/
public interface RetryStrategy {
/** The HTTP header for the number of seconds to wait for a retry when an invocation is throttled. */
String THROTTLED_RETRY_AFTER_HEADER = "Retry-After";
/** The default amount of seconds to wait for a throttled response. */
Long DEFAULT_RETRY_AFTER_SECONDS = Long.valueOf(1L);
/** The throttled HTTP response code. */
int THROTTLED_RESPONSE_CODE = 429;
/**
* Executes the strategy to invoke the {@link RetriableCallResponse} call.
*
* @param retriable the call to invoke
* @return the response
*/
RetriableCallResponse invoke(Retriable retriable);
/**
* Validates the response code for a response.
*
* @param response the response to evaluate
* @throws ThrottledException if a response was throttled
* @throws RequestException if a response contains a 400-based response code value
* @throws ResponseException if a response contains a non 400-based response code value
*/
default void validateResponseCode(final Response response) {
if (response.isSuccessful()) {
return;
}
final int code = response.code();
final boolean isRequestError = (code / 100 == 4);
if (!isRequestError) {
throw new ResponseException("Unsuccessful response (" + code + "): " + response);
}
if (code == THROTTLED_RESPONSE_CODE) {
final Long retryAfterSeconds = extractRetryAfterHeaderValue(response);
final String msg = "Request throttled. Retry after " + retryAfterSeconds + " seconds";
throw new ThrottledException(msg, retryAfterSeconds);
}
throw new RequestException("Error with request (" + code + "): " + response);
}
/**
* Extracts the defined throttle retry value from the response header, or the default if none is defined.
*
* @param response the response
* @return the amount of time in seconds to wait before the next retry
*/
default Long extractRetryAfterHeaderValue(final Response response) {
final String retryAfterHeaderValue = response.header(THROTTLED_RETRY_AFTER_HEADER);
return StringUtils.isNotBlank(retryAfterHeaderValue)
? Long.valueOf(retryAfterHeaderValue)
: DEFAULT_RETRY_AFTER_SECONDS;
}
}