io.github.sornerol.chess.pubapi.client.PubApiClientBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chesscom-pubapi-wrapper Show documentation
Show all versions of chesscom-pubapi-wrapper Show documentation
A wrapper for Chess.com's PubAPI.
The newest version!
package io.github.sornerol.chess.pubapi.client;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.sornerol.chess.pubapi.client.enums.ResponseCode;
import io.github.sornerol.chess.pubapi.client.enums.RetryStrategy;
import io.github.sornerol.chess.pubapi.exception.ChessComPubApiException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.message.BasicHeader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* The base class of all the PubAPI client classes.
*/
@Slf4j
abstract class PubApiClientBase {
/**
* Default value for retryInterval.
*/
public static final Integer DEFAULT_RETRY_INTERVAL = 3;
/**
* Default value for maxRetries.
*/
public static final Integer DEFAULT_MAX_RETRIES = 2;
/**
* Default value for retryStrategy.
*/
public static final RetryStrategy DEFAULT_RETRY_STRATEGY = RetryStrategy.RETRY_N_TIMES;
/**
* If supplied, the userAgent value will be passed in the User-Agent header when making requests to Chess.com.
* By specifying this value with your contact information, Chess.com can contact you in the event they need to
* block access for your application.
*
* @see Rate Limiting
*/
@Getter
@Setter
private String userAgent;
/**
* Number of seconds to wait before retrying if a request fails due to a 429 response code. If retryStrategy is set
* to NEVER, this field does nothing.
*
* @see Rate Limiting
*/
@Getter
@Setter
private Integer retryInterval;
/**
* Maximum number of retries when retryStrategy is RETRY_N_TIMES. This does not include the initial attempt
* (e.g. if maxRetries is 2, the client will make three total attempts before giving up).
*
* @see Rate Limiting
*/
@Getter
@Setter
private Integer maxRetries;
/**
* Defines the behavior to use when receiving a 429 response from the Chess.com API.
*
* @see Rate Limiting
*/
@Getter
@Setter
private RetryStrategy retryStrategy;
/**
* Determines whether the {@link ObjectMapper} should fail if the response contains an
* unknown property. For most applications, this value should be false. Turning this feature
* on is really only useful for testing scenarios.
*/
@Getter
@Setter
private Boolean failOnUnknownProperties;
private final CloseableHttpClient httpClient;
protected PubApiClientBase() {
httpClient = HttpClients.createDefault();
retryInterval = DEFAULT_RETRY_INTERVAL;
maxRetries = DEFAULT_MAX_RETRIES;
retryStrategy = DEFAULT_RETRY_STRATEGY;
failOnUnknownProperties = false;
}
/**
* Execute a GET request on the specified endpoint and deserialize the response into a domain object.
*
* @param endpoint The full Chess.com PubAPI URL.
* @param clazz The class of the object to return.
* @param TDescribes the type parameter.
* @return The deserialized object of the type specified.
* @throws IOException if there is a problem connecting to Chess.com.
* @throws ChessComPubApiException if Chess.com returns a non-success response code.
*/
protected T getRequest(String endpoint, Class clazz) throws IOException, ChessComPubApiException {
String responseJson = getRequest(endpoint);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
return objectMapper.readValue(responseJson, clazz);
}
/**
* Execute a GET request on the specified endpoint and return the response as a String.
*
* @param endpoint The full Chess.com PubAPI URL.
* @return The deserialized object of the type specified.
* @throws IOException if there is a problem connecting to Chess.com.
* @throws ChessComPubApiException if Chess.com returns a non-success response code.
*/
protected String getRequest(String endpoint) throws IOException, ChessComPubApiException {
HttpGet request = new HttpGet(endpoint);
if (userAgent != null) {
request.addHeader(new BasicHeader("User-Agent", userAgent));
} else {
log.warn("No User-Agent provided. Your request may not work. See " +
"https://www.chess.com/announcements/view/breaking-change-user-agent-contact-info-required for" +
"additional details");
}
String responseBody;
Integer attempts = 1;
boolean keepTrying = false;
do {
try (CloseableHttpResponse response = httpClient.execute(request)) {
int statusCode = response.getCode();
if (statusCode == ResponseCode.RATE_LIMIT_EXCEEDED.getValue() && shouldTryRequestAgain(attempts)) {
keepTrying = true;
attempts++;
Thread.sleep(retryInterval * 1000);
} else if (statusCode != ResponseCode.OK.getValue()) {
throw new ChessComPubApiException("Error executing GET request: API returned status code " + statusCode);
}
InputStream inputStream = response.getEntity().getContent();
responseBody = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new ChessComPubApiException("Error while executing request: " + e.getMessage());
}
} while (keepTrying);
return responseBody;
}
private boolean shouldTryRequestAgain(Integer attemptsSoFar) {
if (retryStrategy == RetryStrategy.RETRY_FOREVER) {
return true;
}
return retryStrategy == RetryStrategy.RETRY_N_TIMES && attemptsSoFar <= maxRetries;
}
}