mirror of https://github.com/shred/acme4j
Evaluate rate-limit relation when rate limit is exceeded
parent
57194ce0fc
commit
957dfd71a1
|
@ -16,6 +16,7 @@ package org.shredzone.acme4j.connector;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -85,7 +86,8 @@ public interface Connection extends AutoCloseable {
|
|||
/**
|
||||
* Gets a relation link from the header.
|
||||
* <p>
|
||||
* Relative links are resolved against the last request's URL.
|
||||
* Relative links are resolved against the last request's URL. If there is more than
|
||||
* one relation, the first one is returned.
|
||||
*
|
||||
* @param relation
|
||||
* Link relation
|
||||
|
@ -93,6 +95,17 @@ public interface Connection extends AutoCloseable {
|
|||
*/
|
||||
URI getLink(String relation);
|
||||
|
||||
/**
|
||||
* Gets one or more relation link from the header.
|
||||
* <p>
|
||||
* Relative links are resolved against the last request's URL.
|
||||
*
|
||||
* @param relation
|
||||
* Link relation
|
||||
* @return Collection of links, or {@code null} if there was no such relation link
|
||||
*/
|
||||
Collection<URI> getLinks(String relation);
|
||||
|
||||
/**
|
||||
* Returns the moment returned in a "Retry-After" header.
|
||||
*
|
||||
|
|
|
@ -27,6 +27,8 @@ import java.security.KeyPair;
|
|||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -252,8 +254,24 @@ public class DefaultConnection implements Connection {
|
|||
|
||||
@Override
|
||||
public URI getLink(String relation) {
|
||||
Collection<URI> links = getLinks(relation);
|
||||
if (links == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (links.size() > 1) {
|
||||
LOG.debug("Link: {} - using the first of {}", relation, links.size());
|
||||
}
|
||||
|
||||
return links.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<URI> getLinks(String relation) {
|
||||
assertConnectionIsOpen();
|
||||
|
||||
List<URI> result = new ArrayList<>();
|
||||
|
||||
List<String> links = conn.getHeaderFields().get("Link");
|
||||
if (links != null) {
|
||||
Pattern p = Pattern.compile("<(.*?)>\\s*;\\s*rel=\"?"+ Pattern.quote(relation) + "\"?");
|
||||
|
@ -262,12 +280,12 @@ public class DefaultConnection implements Connection {
|
|||
if (m.matches()) {
|
||||
String location = m.group(1);
|
||||
LOG.debug("Link: {} -> {}", relation, location);
|
||||
return resolveRelative(location);
|
||||
result.add(resolveRelative(location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return (!result.isEmpty() ? result : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -324,7 +342,8 @@ public class DefaultConnection implements Connection {
|
|||
|
||||
case ACME_ERROR_PREFIX + "rateLimited":
|
||||
case ACME_ERROR_PREFIX_DEPRECATED + "rateLimited":
|
||||
throw new AcmeRateLimitExceededException(type, detail, getRetryAfterHeader());
|
||||
throw new AcmeRateLimitExceededException(
|
||||
type, detail, getRetryAfterHeader(), getLinks("rate-limit"));
|
||||
|
||||
default:
|
||||
throw new AcmeServerException(type, detail);
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.exception;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
|
@ -22,6 +25,7 @@ public class AcmeRateLimitExceededException extends AcmeServerException {
|
|||
private static final long serialVersionUID = 4150484059796413069L;
|
||||
|
||||
private final Date retryAfter;
|
||||
private final Collection<URI> documents;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AcmeRateLimitExceededException}.
|
||||
|
@ -34,10 +38,13 @@ public class AcmeRateLimitExceededException extends AcmeServerException {
|
|||
* @param retryAfter
|
||||
* The moment the request is expected to succeed again, may be {@code null}
|
||||
* if not known
|
||||
* @param documents
|
||||
* URIs pointing to documents about the rate limit that was hit
|
||||
*/
|
||||
public AcmeRateLimitExceededException(String type, String detail, Date retryAfter) {
|
||||
public AcmeRateLimitExceededException(String type, String detail, Date retryAfter, Collection<URI> documents) {
|
||||
super(type, detail);
|
||||
this.retryAfter = retryAfter;
|
||||
this.documents = (documents != null ? Collections.unmodifiableCollection(documents) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,4 +55,12 @@ public class AcmeRateLimitExceededException extends AcmeServerException {
|
|||
return (retryAfter != null ? new Date(retryAfter.getTime()) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of URIs pointing to documents about the rate limit that was hit.
|
||||
* {@code null} if the server did not provide such URIs.
|
||||
*/
|
||||
public Collection<URI> getDocuments() {
|
||||
return documents;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -189,6 +189,30 @@ public class DefaultConnectionTest {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that multiple link headers are evaluated.
|
||||
*/
|
||||
@Test
|
||||
public void testGetMultiLink() {
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
headers.put("Link", Arrays.asList(
|
||||
"<https://example.com/acme/terms1>; rel=\"terms-of-service\"",
|
||||
"<https://example.com/acme/terms2>; rel=\"terms-of-service\"",
|
||||
"<https://example.com/acme/terms3>; rel=\"terms-of-service\""
|
||||
));
|
||||
|
||||
when(mockUrlConnection.getHeaderFields()).thenReturn(headers);
|
||||
|
||||
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
assertThat(conn.getLinks("terms-of-service"), containsInAnyOrder(
|
||||
URI.create("https://example.com/acme/terms1"),
|
||||
URI.create("https://example.com/acme/terms2"),
|
||||
URI.create("https://example.com/acme/terms3")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that no Location header returns {@code null}.
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.shredzone.acme4j.connector;
|
|||
|
||||
import java.net.URI;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -63,6 +64,11 @@ public class DummyConnection implements Connection {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<URI> getLinks(String relation) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getRetryAfterHeader() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
Loading…
Reference in New Issue