Skip to content

Commit

Permalink
Merge pull request #153 from rvillablanca/rate-limits
Browse files Browse the repository at this point in the history
support to retrieve rate limits values from response headers #99
  • Loading branch information
cocojoe committed Sep 25, 2018
2 parents 1bbdbcf + 6cc592a commit 66d189e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
50 changes: 50 additions & 0 deletions src/main/java/com/auth0/exception/RateLimitException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.auth0.exception;

/**
* Represents a server error when a rate limit has been exceeded.
* <p>
* Getters for {@code limit, remaining} and {@code reset} corresponds to {@code X-RateLimit-Limit, X-RateLimit-Remaining} and {@code X-RateLimit-Reset} HTTP headers.
* If the value of any headers is missing, then a default value -1 will assigned.
* <p>
* To learn more about rate limits, visit <a href="https://auth0.com/docs/policies/rate-limits">https://auth0.com/docs/policies/rate-limits</a>
*/
public class RateLimitException extends APIException {

private final long limit;
private final long remaining;
private final long reset;

private static final int STATUS_CODE_TOO_MANY_REQUEST = 429;

public RateLimitException(long limit, long remaining, long reset) {
super("Rate limit reached", STATUS_CODE_TOO_MANY_REQUEST, null);
this.limit = limit;
this.remaining = remaining;
this.reset = reset;
}

/**
* Getter for the maximum number of requests available in the current time frame.
* @return The maximum number of requests or -1 if missing.
*/
public long getLimit() {
return limit;
}

/**
* Getter for the number of remaining requests in the current time frame.
* @return Number of remaining requests or -1 if missing.
*/
public long getRemaining() {
return remaining;
}

/**
* Getter for the UNIX timestamp of the expected time when the rate limit will reset.
* @return The UNIX timestamp or -1 if missing.
*/
public long getReset() {
return reset;
}

}
16 changes: 16 additions & 0 deletions src/main/java/com/auth0/net/CustomRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.auth0.exception.APIException;
import com.auth0.exception.Auth0Exception;
import com.auth0.exception.RateLimitException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -15,6 +16,7 @@

@SuppressWarnings("WeakerAccess")
public class CustomRequest<T> extends BaseRequest<T> implements CustomizableRequest<T> {

private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";

private final String url;
Expand All @@ -25,6 +27,8 @@ public class CustomRequest<T> extends BaseRequest<T> implements CustomizableRequ
private final Map<String, Object> parameters;
private Object body;

private static final int STATUS_CODE_TOO_MANY_REQUEST = 429;

CustomRequest(OkHttpClient client, String url, String method, ObjectMapper mapper, TypeReference<T> tType) {
super(client);
this.url = url;
Expand Down Expand Up @@ -96,6 +100,10 @@ protected RequestBody createBody() throws Auth0Exception {
}

protected Auth0Exception createResponseException(Response response) {
if (response.code() == STATUS_CODE_TOO_MANY_REQUEST) {
return createRateLimitException(response);
}

String payload = null;
try (ResponseBody body = response.body()) {
payload = body.string();
Expand All @@ -106,4 +114,12 @@ protected Auth0Exception createResponseException(Response response) {
return new APIException(payload, response.code(), e);
}
}

private RateLimitException createRateLimitException(Response response) {
// -1 as default value if the header could not be found.
long limit = Long.parseLong(response.header("X-RateLimit-Limit", "-1"));
long remaining = Long.parseLong(response.header("X-RateLimit-Remaining", "-1"));
long reset = Long.parseLong(response.header("X-RateLimit-Reset", "-1"));
return new RateLimitException(limit, remaining, reset);
}
}
15 changes: 14 additions & 1 deletion src/test/java/com/auth0/client/MockServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ public class MockServer {
public static final String MGMT_EMPTY_LIST = "src/test/resources/mgmt/empty_list.json";
public static final String MGMT_JOB_POST_VERIFICATION_EMAIL = "src/test/resources/mgmt/post_verification_email.json";


private final MockWebServer server;

public MockServer() throws Exception {
Expand Down Expand Up @@ -108,6 +107,20 @@ public void jsonResponse(String path, int statusCode) throws IOException {
server.enqueue(response);
}

public void rateLimitReachedResponse(long limit, long remaining, long reset) throws IOException {
MockResponse response = new MockResponse().setResponseCode(429);
if (limit != -1) {
response.addHeader("X-RateLimit-Limit", String.valueOf(limit));
}
if (remaining != -1) {
response.addHeader("X-RateLimit-Remaining", String.valueOf(remaining));
}
if (reset != -1) {
response.addHeader("X-RateLimit-Reset", String.valueOf(reset));
}
server.enqueue(response);
}

public void textResponse(String path, int statusCode) throws IOException {
MockResponse response = new MockResponse()
.setResponseCode(statusCode)
Expand Down
53 changes: 52 additions & 1 deletion src/test/java/com/auth0/net/CustomRequestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;

import static com.auth0.client.MockServer.*;
import com.auth0.exception.RateLimitException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -301,5 +302,55 @@ public void shouldParsePlainTextErrorResponse() throws Exception {
assertThat(authException.getValue("non_existing_key"), is(nullValue()));
assertThat(authException.getStatusCode(), is(400));
}

@Test
public void shouldParseRateLimitsHeaders() throws Exception {
CustomRequest<List> request = new CustomRequest<>(client, server.getBaseUrl(), "GET", listType);
server.rateLimitReachedResponse(100, 10, 5);
Exception exception = null;
try {
request.execute();
server.takeRequest();
} catch (Exception e) {
exception = e;
}
assertThat(exception, is(notNullValue()));
assertThat(exception, is(instanceOf(RateLimitException.class)));
assertThat(exception.getCause(), is(nullValue()));
assertThat(exception.getMessage(), is("Request failed with status code 429: Rate limit reached"));
RateLimitException rateLimitException = (RateLimitException) exception;
assertThat(rateLimitException.getDescription(), is("Rate limit reached"));
assertThat(rateLimitException.getError(), is(nullValue()));
assertThat(rateLimitException.getValue("non_existing_key"), is(nullValue()));
assertThat(rateLimitException.getStatusCode(), is(429));
assertThat(rateLimitException.getLimit(), is(100L));
assertThat(rateLimitException.getRemaining(), is(10L));
assertThat(rateLimitException.getReset(), is(5L));
}

@Test
public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception {
CustomRequest<List> request = new CustomRequest<>(client, server.getBaseUrl(), "GET", listType);
server.rateLimitReachedResponse(-1, -1, -1);
Exception exception = null;
try {
request.execute();
server.takeRequest();
} catch (Exception e) {
exception = e;
}
assertThat(exception, is(notNullValue()));
assertThat(exception, is(instanceOf(RateLimitException.class)));
assertThat(exception.getCause(), is(nullValue()));
assertThat(exception.getMessage(), is("Request failed with status code 429: Rate limit reached"));
RateLimitException rateLimitException = (RateLimitException) exception;
assertThat(rateLimitException.getDescription(), is("Rate limit reached"));
assertThat(rateLimitException.getError(), is(nullValue()));
assertThat(rateLimitException.getValue("non_existing_key"), is(nullValue()));
assertThat(rateLimitException.getStatusCode(), is(429));
assertThat(rateLimitException.getLimit(), is(-1L));
assertThat(rateLimitException.getRemaining(), is(-1L));
assertThat(rateLimitException.getReset(), is(-1L));
}

}
}

0 comments on commit 66d189e

Please sign in to comment.