Add action queuing and rate limit handling
This commit is contained in:
parent
93fde2621d
commit
5e99ef7fea
@ -25,10 +25,16 @@ package me.braydon.pelican.action;
|
|||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
import me.braydon.pelican.model.PanelModel;
|
import me.braydon.pelican.model.PanelModel;
|
||||||
import me.braydon.pelican.request.JsonWebRequest;
|
import me.braydon.pelican.request.JsonWebRequest;
|
||||||
import me.braydon.pelican.request.WebRequestHandler;
|
import me.braydon.pelican.request.RateLimitHandler;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An action that can be executed on a panel.
|
* An action that can be executed on a panel.
|
||||||
@ -39,14 +45,19 @@ import me.braydon.pelican.request.WebRequestHandler;
|
|||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class PanelAction<T extends PanelModel<T>> {
|
public class PanelAction<T extends PanelModel<T>> {
|
||||||
/**
|
/**
|
||||||
* The web handler to execute actions with.
|
* The client config to use for executing actions.
|
||||||
*/
|
*/
|
||||||
@NonNull private final WebRequestHandler requestHandler;
|
@NonNull private final ClientConfig clientConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rate limit handler to use for querying actions.
|
||||||
|
*/
|
||||||
|
@NonNull private final RateLimitHandler rateLimitHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The web request this action will execute.
|
* The web request this action will execute.
|
||||||
*/
|
*/
|
||||||
@NonNull private final JsonWebRequest webRequest;
|
@Getter @NonNull private final JsonWebRequest webRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of response expected when
|
* The type of response expected when
|
||||||
@ -54,27 +65,56 @@ public class PanelAction<T extends PanelModel<T>> {
|
|||||||
*/
|
*/
|
||||||
private final Class<T> responseType;
|
private final Class<T> responseType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue this action and await its response.
|
||||||
|
* The given callback will be invoked once
|
||||||
|
* a response has been received from the panel.
|
||||||
|
*
|
||||||
|
* @param callback the callback to invoke
|
||||||
|
*/
|
||||||
|
public void queue(@NonNull Consumer<T> callback) {
|
||||||
|
queue((res, ex) -> {
|
||||||
|
if (ex != null) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
callback.accept(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue this action and await its response.
|
||||||
|
* The given callback will be invoked once
|
||||||
|
* a response has been received from the panel.
|
||||||
|
*
|
||||||
|
* @param callback the callback to invoke
|
||||||
|
*/
|
||||||
|
public void queue(@NonNull BiConsumer<T, Exception> callback) {
|
||||||
|
CompletableFuture.runAsync(() -> rateLimitHandler.tryRequest(this, callback, false, true));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute this action instantly.
|
* Execute this action instantly.
|
||||||
*
|
*
|
||||||
* @return the response, null if none
|
* @return the response, null if none
|
||||||
*/
|
*/
|
||||||
public T execute() {
|
public T execute() {
|
||||||
return requestHandler.handle(webRequest, responseType);
|
return webRequest.execute(clientConfig, responseType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new panel action.
|
* Create a new panel action.
|
||||||
*
|
*
|
||||||
* @param requestHandler the request handler to use
|
* @param clientConfig the client config to use for executing actions
|
||||||
|
* @param rateLimitHandler the rate limit handler to use for querying actions
|
||||||
* @param webRequest the web request to send for this action
|
* @param webRequest the web request to send for this action
|
||||||
* @param responseType the expected response type, null if none
|
* @param responseType the expected response type, null if none
|
||||||
* @param <T> the response type
|
* @param <T> the response type
|
||||||
* @return the panel action
|
* @return the panel action
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static <T extends PanelModel<T>> PanelAction<T> create(@NonNull WebRequestHandler requestHandler,
|
public static <T extends PanelModel<T>> PanelAction<T> create(@NonNull ClientConfig clientConfig,
|
||||||
|
@NonNull RateLimitHandler rateLimitHandler,
|
||||||
@NonNull JsonWebRequest webRequest, Class<T> responseType) {
|
@NonNull JsonWebRequest webRequest, Class<T> responseType) {
|
||||||
return new PanelAction<>(requestHandler, webRequest, responseType);
|
return new PanelAction<>(clientConfig, rateLimitHandler, webRequest, responseType);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -28,7 +28,8 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import me.braydon.pelican.request.WebRequestHandler;
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
|
import me.braydon.pelican.request.RateLimitHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of actions that can
|
* A set of actions that can
|
||||||
@ -39,7 +40,12 @@ import me.braydon.pelican.request.WebRequestHandler;
|
|||||||
@AllArgsConstructor @Getter(AccessLevel.PROTECTED) @Accessors(fluent = true)
|
@AllArgsConstructor @Getter(AccessLevel.PROTECTED) @Accessors(fluent = true)
|
||||||
public abstract class PanelActions {
|
public abstract class PanelActions {
|
||||||
/**
|
/**
|
||||||
* The request handler to use for action execution.
|
* The client config to use for executing actions.
|
||||||
*/
|
*/
|
||||||
@NonNull private final WebRequestHandler requestHandler;
|
@NonNull private final ClientConfig clientConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rate limit handler to use for querying actions.
|
||||||
|
*/
|
||||||
|
@NonNull private final RateLimitHandler rateLimitHandler;
|
||||||
}
|
}
|
@ -25,7 +25,8 @@ package me.braydon.pelican.action.pelican;
|
|||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import me.braydon.pelican.action.pterodactyl.PteroPanelActions;
|
import me.braydon.pelican.action.pterodactyl.PteroPanelActions;
|
||||||
import me.braydon.pelican.request.WebRequestHandler;
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
|
import me.braydon.pelican.request.RateLimitHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented actions for the Pelican panel.
|
* Implemented actions for the Pelican panel.
|
||||||
@ -34,7 +35,7 @@ import me.braydon.pelican.request.WebRequestHandler;
|
|||||||
* @see <a href="https://pelican.dev">Pelican Website</a>
|
* @see <a href="https://pelican.dev">Pelican Website</a>
|
||||||
*/
|
*/
|
||||||
public class PelicanPanelActions extends PteroPanelActions {
|
public class PelicanPanelActions extends PteroPanelActions {
|
||||||
public PelicanPanelActions(@NonNull WebRequestHandler requestHandler) {
|
public PelicanPanelActions(@NonNull ClientConfig clientConfig, @NonNull RateLimitHandler rateLimitHandler) {
|
||||||
super(requestHandler);
|
super(clientConfig, rateLimitHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -28,7 +28,8 @@ import lombok.NonNull;
|
|||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import me.braydon.pelican.action.PanelActions;
|
import me.braydon.pelican.action.PanelActions;
|
||||||
import me.braydon.pelican.action.pterodactyl.application.ApplicationNodeActions;
|
import me.braydon.pelican.action.pterodactyl.application.ApplicationNodeActions;
|
||||||
import me.braydon.pelican.request.WebRequestHandler;
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
|
import me.braydon.pelican.request.RateLimitHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented actions for the Pterodactyl panel.
|
* Implemented actions for the Pterodactyl panel.
|
||||||
@ -43,8 +44,8 @@ public class PteroPanelActions extends PanelActions {
|
|||||||
*/
|
*/
|
||||||
@NonNull private final Application application;
|
@NonNull private final Application application;
|
||||||
|
|
||||||
public PteroPanelActions(@NonNull WebRequestHandler requestHandler) {
|
public PteroPanelActions(@NonNull ClientConfig clientConfig, @NonNull RateLimitHandler rateLimitHandler) {
|
||||||
super(requestHandler);
|
super(clientConfig, rateLimitHandler);
|
||||||
this.application = new Application();
|
this.application = new Application();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +57,6 @@ public class PteroPanelActions extends PanelActions {
|
|||||||
/**
|
/**
|
||||||
* Node actions for the application.
|
* Node actions for the application.
|
||||||
*/
|
*/
|
||||||
@NonNull private final ApplicationNodeActions nodes = new ApplicationNodeActions(requestHandler());
|
@NonNull private final ApplicationNodeActions nodes = new ApplicationNodeActions(clientConfig(), rateLimitHandler());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,9 +26,10 @@ package me.braydon.pelican.action.pterodactyl.application;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import me.braydon.pelican.action.PanelAction;
|
import me.braydon.pelican.action.PanelAction;
|
||||||
import me.braydon.pelican.action.PanelActions;
|
import me.braydon.pelican.action.PanelActions;
|
||||||
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
import me.braydon.pelican.model.Node;
|
import me.braydon.pelican.model.Node;
|
||||||
import me.braydon.pelican.request.JsonWebRequest;
|
import me.braydon.pelican.request.JsonWebRequest;
|
||||||
import me.braydon.pelican.request.WebRequestHandler;
|
import me.braydon.pelican.request.RateLimitHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application node actions
|
* Application node actions
|
||||||
@ -37,8 +38,8 @@ import me.braydon.pelican.request.WebRequestHandler;
|
|||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
public final class ApplicationNodeActions extends PanelActions {
|
public final class ApplicationNodeActions extends PanelActions {
|
||||||
public ApplicationNodeActions(@NonNull WebRequestHandler requestHandler) {
|
public ApplicationNodeActions(@NonNull ClientConfig clientConfig, @NonNull RateLimitHandler rateLimitHandler) {
|
||||||
super(requestHandler);
|
super(clientConfig, rateLimitHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +50,7 @@ public final class ApplicationNodeActions extends PanelActions {
|
|||||||
* @return the action
|
* @return the action
|
||||||
*/
|
*/
|
||||||
public PanelAction<Node> details(int id) {
|
public PanelAction<Node> details(int id) {
|
||||||
return PanelAction.create(requestHandler(), JsonWebRequest.builder()
|
return PanelAction.create(clientConfig(), rateLimitHandler(), JsonWebRequest.builder()
|
||||||
.endpoint("/application/nodes/" + id)
|
.endpoint("/application/nodes/" + id)
|
||||||
.build(), Node.class);
|
.build(), Node.class);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import me.braydon.pelican.action.PanelActions;
|
import me.braydon.pelican.action.PanelActions;
|
||||||
import me.braydon.pelican.action.pelican.PelicanPanelActions;
|
import me.braydon.pelican.action.pelican.PelicanPanelActions;
|
||||||
import me.braydon.pelican.action.pterodactyl.PteroPanelActions;
|
import me.braydon.pelican.action.pterodactyl.PteroPanelActions;
|
||||||
import me.braydon.pelican.request.WebRequestHandler;
|
import me.braydon.pelican.request.RateLimitHandler;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
|
||||||
@ -57,7 +57,9 @@ public final class Pelican4J<A extends PanelActions> implements Closeable {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private Pelican4J(@NonNull ClientConfig config, @NonNull Class<A> actionsClass) {
|
private Pelican4J(@NonNull ClientConfig config, @NonNull Class<A> actionsClass) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
actions = actionsClass.getConstructor(WebRequestHandler.class).newInstance(new WebRequestHandler(config));
|
actions = actionsClass.getConstructor(ClientConfig.class, RateLimitHandler.class).newInstance(
|
||||||
|
config, new RateLimitHandler(config)
|
||||||
|
);
|
||||||
if (config.debugging()) {
|
if (config.debugging()) {
|
||||||
log.debug("Created a new {} client: {}", actionsClass == PelicanPanelActions.class ? "Pelican" : "Ptero", config);
|
log.debug("Created a new {} client: {}", actionsClass == PelicanPanelActions.class ? "Pelican" : "Ptero", config);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ import com.google.gson.JsonObject;
|
|||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.braydon.pelican.client.ClientConfig;
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
import me.braydon.pelican.exception.PanelAPIException;
|
import me.braydon.pelican.exception.PanelAPIException;
|
||||||
import me.braydon.pelican.model.PanelModel;
|
import me.braydon.pelican.model.PanelModel;
|
||||||
@ -39,7 +41,8 @@ import okhttp3.*;
|
|||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
@Builder
|
@Builder @ToString
|
||||||
|
@Slf4j(topic = "Web Request")
|
||||||
public class JsonWebRequest {
|
public class JsonWebRequest {
|
||||||
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
|
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
|
||||||
private static final MediaType JSON_MEDIA = MediaType.get("application/json");
|
private static final MediaType JSON_MEDIA = MediaType.get("application/json");
|
||||||
@ -71,10 +74,17 @@ public class JsonWebRequest {
|
|||||||
* @param <T> the response type
|
* @param <T> the response type
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected <T extends PanelModel<T>> T execute(@NonNull ClientConfig clientConfig, Class<T> responseType) {
|
public <T extends PanelModel<T>> T execute(@NonNull ClientConfig clientConfig, Class<T> responseType) {
|
||||||
|
String endpoint = clientConfig.panelUrl() + "/api" + this.endpoint;
|
||||||
|
if (clientConfig.debugging()) {
|
||||||
|
log.debug("Sending a {} request to {}...", method, endpoint);
|
||||||
|
if (body != null) {
|
||||||
|
log.debug("With Body: {}", body);
|
||||||
|
}
|
||||||
|
}
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.method(method.name(), body == null ? null : RequestBody.create(body, JSON_MEDIA))
|
.method(method.name(), body == null ? null : RequestBody.create(body, JSON_MEDIA))
|
||||||
.url(clientConfig.panelUrl() + "/api" + endpoint)
|
.url(endpoint)
|
||||||
.addHeader("Content-Type", "application/json")
|
.addHeader("Content-Type", "application/json")
|
||||||
.addHeader("Accept", "Application/vnd.pterodactyl.v1+json")
|
.addHeader("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||||
.addHeader("Authorization", "Bearer " + clientConfig.apiKey())
|
.addHeader("Authorization", "Bearer " + clientConfig.apiKey())
|
||||||
@ -85,6 +95,10 @@ public class JsonWebRequest {
|
|||||||
int status = response.code(); // The HTTP response code
|
int status = response.code(); // The HTTP response code
|
||||||
String json = response.body().string(); // The json response
|
String json = response.body().string(); // The json response
|
||||||
|
|
||||||
|
if (clientConfig.debugging()) {
|
||||||
|
log.debug("Receive response: {}", status);
|
||||||
|
}
|
||||||
|
|
||||||
// If the status is not 200 (OK), handle the error
|
// If the status is not 200 (OK), handle the error
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
JsonObject errorJsonObject = GSON.fromJson(json, JsonObject.class);
|
JsonObject errorJsonObject = GSON.fromJson(json, JsonObject.class);
|
||||||
|
@ -23,33 +23,43 @@
|
|||||||
*/
|
*/
|
||||||
package me.braydon.pelican.request;
|
package me.braydon.pelican.request;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import me.braydon.pelican.client.ClientConfig;
|
import me.braydon.pelican.action.PanelAction;
|
||||||
import me.braydon.pelican.model.PanelModel;
|
import me.braydon.pelican.model.PanelModel;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The handler for processing web requests.
|
* Represents a queued action.
|
||||||
*
|
*
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor(access = AccessLevel.PROTECTED) @Getter
|
||||||
public final class WebRequestHandler {
|
public class QueuedAction<T extends PanelModel<T>> {
|
||||||
/**
|
/**
|
||||||
* The client config used to make requests.
|
* The action that's queued.
|
||||||
*/
|
*/
|
||||||
@NonNull private final ClientConfig clientConfig;
|
@NonNull private final PanelAction<?> action;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the given web request.
|
* The callback to invoke when
|
||||||
*
|
* this action is executed.
|
||||||
* @param request the request to handle
|
|
||||||
* @param responseType the expected response type, null if none
|
|
||||||
* @return the response, null if none
|
|
||||||
* @param <T> the response type
|
|
||||||
*/
|
*/
|
||||||
public <T extends PanelModel<T>> T handle(@NonNull JsonWebRequest request, Class<T> responseType) {
|
@NonNull private final BiConsumer<T, Exception> callback;
|
||||||
// TODO: handle rate limit handling for async reqs
|
|
||||||
return request.execute(clientConfig, responseType);
|
/**
|
||||||
|
* The amount of times this
|
||||||
|
* action has been retried.
|
||||||
|
*/
|
||||||
|
private int retries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the retry count.
|
||||||
|
*/
|
||||||
|
protected void incrementRetries() {
|
||||||
|
retries++;
|
||||||
}
|
}
|
||||||
}
|
}
|
146
src/main/java/me/braydon/pelican/request/RateLimitHandler.java
Normal file
146
src/main/java/me/braydon/pelican/request/RateLimitHandler.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Braydon (Rainnny).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package me.braydon.pelican.request;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.braydon.pelican.action.PanelAction;
|
||||||
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
|
import me.braydon.pelican.exception.PanelAPIException;
|
||||||
|
import me.braydon.pelican.model.PanelModel;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for handling panel rate limits.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@Slf4j(topic = "Rate Limit Handler")
|
||||||
|
public final class RateLimitHandler {
|
||||||
|
/**
|
||||||
|
* The interval at which requests should be retried at if they are rate limited.
|
||||||
|
*/
|
||||||
|
private static final long INTERVAL = TimeUnit.SECONDS.toMillis(2L);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of times a queued action should
|
||||||
|
* be retried before failing the action.
|
||||||
|
*/
|
||||||
|
private static final int MAX_RETRIES = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client config to use.
|
||||||
|
*/
|
||||||
|
@NonNull private final ClientConfig clientConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently queued actions, awaiting to be re-tried.
|
||||||
|
*/
|
||||||
|
private final List<QueuedAction<?>> queuedActions = new LinkedList<>();
|
||||||
|
|
||||||
|
public RateLimitHandler(@NonNull ClientConfig clientConfig) {
|
||||||
|
this.clientConfig = clientConfig;
|
||||||
|
|
||||||
|
// Schedule a task to process the queue.
|
||||||
|
// This will take the first element in the queue
|
||||||
|
// every X interval, and try to execute it. Each
|
||||||
|
// time the action fails, the retry count will be
|
||||||
|
// incremented. If the action is successful, or reaches
|
||||||
|
// the max retries, it will be removed from the queue.
|
||||||
|
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int queueSize = queuedActions.size(); // The size of the queue
|
||||||
|
if (queueSize == 0) { // Queue is empty
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (clientConfig.debugging()) {
|
||||||
|
log.debug("Processing queue of size {}...", queueSize);
|
||||||
|
}
|
||||||
|
QueuedAction<?> queuedAction = queuedActions.get(0); // Get the first queued action
|
||||||
|
boolean maxedRetries = queuedAction.getRetries() >= MAX_RETRIES; // If the action has maxed retries
|
||||||
|
|
||||||
|
// Re-try the queued action, and if it fails, increment the retry count and move on
|
||||||
|
if (!tryRequest(queuedAction.getAction(), queuedAction.getCallback(), maxedRetries, false)) {
|
||||||
|
queuedAction.incrementRetries();
|
||||||
|
if (!maxedRetries) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The queued action was either successful, or
|
||||||
|
// reached the max retries, remove it from the queue
|
||||||
|
queuedActions.remove(0);
|
||||||
|
}
|
||||||
|
}, INTERVAL, INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try and execute the given action.
|
||||||
|
*
|
||||||
|
* @param action the action to try
|
||||||
|
* @param callback the callback to invoke
|
||||||
|
* @param throwRateLimitErrors whether rate limit errors should be thrown
|
||||||
|
* @param retry should the action be retried if a rate limit is hit (and being thrown)?
|
||||||
|
* @return whether the request was successful
|
||||||
|
* @param <T> the action response type
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends PanelModel<T>> boolean tryRequest(@NonNull PanelAction<?> action, BiConsumer<T, Exception> callback, boolean throwRateLimitErrors, boolean retry) {
|
||||||
|
try { // Try and execute the request
|
||||||
|
if (clientConfig.debugging()) {
|
||||||
|
log.debug("Trying to execute action {}...", action.getWebRequest());
|
||||||
|
}
|
||||||
|
callback.accept((T) action.execute(), null);
|
||||||
|
return true; // Success
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// The API rate limit has been reached, queue
|
||||||
|
// the task to be re-tried later
|
||||||
|
if (ex instanceof PanelAPIException) {
|
||||||
|
PanelAPIException apiException = (PanelAPIException) ex;
|
||||||
|
if (apiException.getCode() == 404 && !throwRateLimitErrors) {
|
||||||
|
if (clientConfig.debugging()) {
|
||||||
|
log.debug("Panel API rate limit exceeded{}", retry ? ", queued action to be re-tried later..." : "");
|
||||||
|
}
|
||||||
|
if (retry) {
|
||||||
|
queuedActions.add(new QueuedAction<>(action, callback, 0));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not a rate limit, invoke
|
||||||
|
// the callback with the error
|
||||||
|
if (clientConfig.debugging()) {
|
||||||
|
log.debug("Failed to execute action:", ex);
|
||||||
|
}
|
||||||
|
callback.accept(null, ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,11 +23,10 @@
|
|||||||
*/
|
*/
|
||||||
package me.braydon.pelican.test;
|
package me.braydon.pelican.test;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import me.braydon.pelican.action.pelican.PelicanPanelActions;
|
import me.braydon.pelican.action.pelican.PelicanPanelActions;
|
||||||
import me.braydon.pelican.client.ClientConfig;
|
import me.braydon.pelican.client.ClientConfig;
|
||||||
import me.braydon.pelican.client.Pelican4J;
|
import me.braydon.pelican.client.Pelican4J;
|
||||||
import me.braydon.pelican.exception.PanelAPIException;
|
|
||||||
import me.braydon.pelican.model.Node;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -65,11 +64,12 @@ public final class PelicanActionTests {
|
|||||||
* Test getting a list of
|
* Test getting a list of
|
||||||
* nodes from the panel.
|
* nodes from the panel.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test @SneakyThrows
|
||||||
void testGetNodes() {
|
void testGetNodes() {
|
||||||
Node node = client.actions().application().nodes().details(-1).execute();
|
client.actions().application().nodes().details(1).queue(node -> {
|
||||||
System.out.println("node = " + node);
|
System.out.println("node = " + node);
|
||||||
// TODO: ...
|
});
|
||||||
|
Thread.sleep(60000L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user