From 23e247ffd6eabfb0825b74bbaf47f3714a160f1a Mon Sep 17 00:00:00 2001 From: Iulian <40763147+iulian03@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:16:46 +0200 Subject: [PATCH] [improvement] rate limits * improvement of how the rate limits are set * small change * change the way a string is converted to integer --------- Co-authored-by: Iulian Masar --- src/main/java/com/mangopay/core/RestTool.java | 125 ++++++++++++------ .../java/com/mangopay/entities/RateLimit.java | 8 +- .../com/mangopay/entities/RateLimitTest.java | 2 +- 3 files changed, 92 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/mangopay/core/RestTool.java b/src/main/java/com/mangopay/core/RestTool.java index 1f5d85f0..27551b35 100644 --- a/src/main/java/com/mangopay/core/RestTool.java +++ b/src/main/java/com/mangopay/core/RestTool.java @@ -25,6 +25,11 @@ */ public class RestTool { + private final static int MINUTES_15 = 15; + private final static int MINUTES_30 = 30; + private final static int MINUTES_60 = 60; + private final static int ONE_DAY_IN_MINUTES = MINUTES_60 * 24; + // root/parent instance that holds the OAuthToken and Configuration instance private MangoPayApi root; @@ -438,50 +443,51 @@ private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagemen } private void readResponseHeaders(HttpURLConnection conn) { - List updatedRateLimits = null; - for (Map.Entry> k : conn.getHeaderFields().entrySet()) { - for (String v : k.getValue()) { + Set>> headers = conn.getHeaderFields().entrySet(); - if (this.debugMode) logger.info("Response header: {}", k.getKey() + ":" + v); + List rateLimitResetValues = new ArrayList<>(); + List rateLimitRemainingValues = new ArrayList<>(); + List rateLimitValues = new ArrayList<>(); - if (k.getKey() == null) continue; + for (Map.Entry> k : headers) { + if (this.debugMode) { + logger.info("Reading rate limit headers"); + } - if (k.getKey().equals("X-RateLimit-Remaining") || k.getKey().equals("X-RateLimit-Remaining".toLowerCase())) { - if (updatedRateLimits == null) { - updatedRateLimits = initRateLimits(); + // prepare rate limits values + if (k.getKey() != null) { + switch (k.getKey().toLowerCase()) { + case "x-ratelimit-reset": { + rateLimitResetValues = k.getValue(); + break; } - List callsRemaining = k.getValue(); - updatedRateLimits.get(0).setCallsRemaining(Integer.valueOf(callsRemaining.get(3))); - updatedRateLimits.get(1).setCallsRemaining(Integer.valueOf(callsRemaining.get(2))); - updatedRateLimits.get(2).setCallsRemaining(Integer.valueOf(callsRemaining.get(1))); - updatedRateLimits.get(3).setCallsRemaining(Integer.valueOf(callsRemaining.get(0))); - } - if (k.getKey().equals("X-RateLimit") || k.getKey().equals("X-RateLimit".toLowerCase())) { - if (updatedRateLimits == null) { - updatedRateLimits = initRateLimits(); + case "x-ratelimit-remaining": { + rateLimitRemainingValues = k.getValue(); + break; } - List callsMade = k.getValue(); - updatedRateLimits.get(0).setCallsMade(Integer.valueOf(callsMade.get(3))); - updatedRateLimits.get(1).setCallsMade(Integer.valueOf(callsMade.get(2))); - updatedRateLimits.get(2).setCallsMade(Integer.valueOf(callsMade.get(1))); - updatedRateLimits.get(3).setCallsMade(Integer.valueOf(callsMade.get(0))); - } - if (k.getKey().equals("X-RateLimit-Reset") || k.getKey().equals("X-RateLimit-Reset".toLowerCase())) { - if (updatedRateLimits == null) { - updatedRateLimits = initRateLimits(); + case "x-ratelimit": { + rateLimitValues = k.getValue(); + break; } - List resetTimes = k.getValue(); - updatedRateLimits.get(0).setResetTimeSeconds(Long.valueOf(resetTimes.get(3))); - updatedRateLimits.get(1).setResetTimeSeconds(Long.valueOf(resetTimes.get(2))); - updatedRateLimits.get(2).setResetTimeSeconds(Long.valueOf(resetTimes.get(1))); - updatedRateLimits.get(3).setResetTimeSeconds(Long.valueOf(resetTimes.get(0))); + default: + break; } + } + + for (String v : k.getValue()) { + if (this.debugMode) logger.info("Response header: {}", k.getKey() + ":" + v); + if (k.getKey() == null) { + continue; + } + if (k.getKey().equals("X-Number-Of-Pages") || k.getKey().equals("X-Number-Of-Pages".toLowerCase())) { this.pagination.setTotalPages(Integer.parseInt(v)); } + if (k.getKey().equals("X-Number-Of-Items") || k.getKey().equals("X-Number-Of-Items".toLowerCase())) { this.pagination.setTotalItems(Integer.parseInt(v)); } + if (k.getKey().equals("Link") || k.getKey().equals("Link".toLowerCase())) { String linkValue = v; String[] links = linkValue.split(","); @@ -505,17 +511,56 @@ private void readResponseHeaders(HttpURLConnection conn) { } } } - if (updatedRateLimits != null) { - root.setRateLimits(updatedRateLimits); - } + + setRateLimits(rateLimitResetValues, rateLimitRemainingValues, rateLimitValues); } - private List initRateLimits() { - return Arrays.asList( - new RateLimit(15), - new RateLimit(30), - new RateLimit(60), - new RateLimit(24 * 60)); + /** + * set rate limits: + * - if the X-RateLimit-Reset is not present, rate limits can't be set + * - if all 3 headers are present, but they don't have the same structure (size), rate limits can't be set + */ + private void setRateLimits( + List rateLimitResetValues, + List rateLimitRemainingValues, + List rateLimitValues + ) { + if (rateLimitResetValues.size() == rateLimitRemainingValues.size() && rateLimitResetValues.size() == rateLimitValues.size()) { + if (this.debugMode) { + logger.info("Setting rate limits"); + } + List rateLimits = new ArrayList<>(); + + // resetTime is in epoch (seconds), currentTime is in ms => convert currentTime to the same unit + long currentTime = System.currentTimeMillis() / 1000; + + for (int i = 0; i < rateLimitResetValues.size(); i++) { + long numberOfMinutes = (Integer.parseInt(rateLimitResetValues.get(i)) - currentTime) / 60; + RateLimit rateLimit = new RateLimit(); + + if (numberOfMinutes <= MINUTES_15) { + rateLimit.setIntervalMinutes(MINUTES_15); + } else if (numberOfMinutes <= MINUTES_30) { + rateLimit.setIntervalMinutes(MINUTES_30); + } else if (numberOfMinutes <= MINUTES_60) { + rateLimit.setIntervalMinutes(MINUTES_60); + } else if (numberOfMinutes <= ONE_DAY_IN_MINUTES) { + rateLimit.setIntervalMinutes(ONE_DAY_IN_MINUTES); + } + + rateLimit.setResetTimeSeconds(Long.parseLong(rateLimitResetValues.get(i))); + rateLimit.setCallsRemaining(Integer.parseInt(rateLimitRemainingValues.get(i))); + rateLimit.setCallsMade(Integer.parseInt(rateLimitValues.get(i))); + + rateLimits.add(rateLimit); + } + + if (!rateLimits.isEmpty()) { + root.setRateLimits(rateLimits); + } + } else if (this.debugMode) { + logger.warn("Can't set rate limits"); + } } public T castResponseToEntity(Class classOfT, JsonObject response) { diff --git a/src/main/java/com/mangopay/entities/RateLimit.java b/src/main/java/com/mangopay/entities/RateLimit.java index 98313acc..4fbe39e2 100644 --- a/src/main/java/com/mangopay/entities/RateLimit.java +++ b/src/main/java/com/mangopay/entities/RateLimit.java @@ -7,8 +7,7 @@ public class RateLimit { private int callsRemaining; private long resetTimeSeconds; - public RateLimit(int intervalMinutes) { - this.intervalMinutes = intervalMinutes; + public RateLimit() { } /** @@ -82,4 +81,9 @@ public long getResetTimeSeconds() { public void setResetTimeSeconds(long resetTimeSeconds) { this.resetTimeSeconds = resetTimeSeconds; } + + public RateLimit setIntervalMinutes(int intervalMinutes) { + this.intervalMinutes = intervalMinutes; + return this; + } } diff --git a/src/test/java/com/mangopay/entities/RateLimitTest.java b/src/test/java/com/mangopay/entities/RateLimitTest.java index dcc2c346..cde72d79 100644 --- a/src/test/java/com/mangopay/entities/RateLimitTest.java +++ b/src/test/java/com/mangopay/entities/RateLimitTest.java @@ -23,6 +23,6 @@ public void rateLimitsUpdateTest() throws Exception { List rateLimits = this.api.getRateLimits(); assertNotNull(rateLimits); - assertTrue(rateLimits.size() == 4); + assertFalse(rateLimits.isEmpty()); } }