Refactor HTTP response handling

pull/30/head
Richard Körber 2016-12-16 01:18:19 +01:00
parent 96d46784c6
commit ce125da4aa
19 changed files with 323 additions and 177 deletions

View File

@ -176,10 +176,8 @@ public class Authorization extends AcmeResource {
public void update() throws AcmeException {
LOG.debug("update");
try (Connection conn = getSession().provider().connect()) {
int rc = conn.sendRequest(getLocation(), getSession());
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendRequest(getLocation(), getSession());
int rc = conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
Map<String, Object> result = conn.readJsonResponse();
unmarshalAuthorization(result);
@ -205,10 +203,8 @@ public class Authorization extends AcmeResource {
claims.putResource("authz");
claims.put("status", "deactivated");
int rc = conn.sendSignedRequest(getLocation(), claims, getSession());
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getLocation(), claims, getSession());
conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
}
}

View File

@ -88,7 +88,8 @@ public class Certificate extends AcmeResource {
if (cert == null) {
LOG.debug("download");
try (Connection conn = getSession().provider().connect()) {
int rc = conn.sendRequest(getLocation(), getSession());
conn.sendRequest(getLocation(), getSession());
int rc = conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
if (rc == HttpURLConnection.HTTP_ACCEPTED) {
Date retryAfter = conn.getRetryAfterHeader();
if (retryAfter != null) {
@ -98,10 +99,6 @@ public class Certificate extends AcmeResource {
}
}
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
chainCertUri = conn.getLink("up");
cert = conn.readCertificate();
}
@ -135,10 +132,8 @@ public class Certificate extends AcmeResource {
URI link = chainCertUri;
while (link != null && certChain.size() < MAX_CHAIN_LENGTH) {
try (Connection conn = getSession().provider().connect()) {
int rc = conn.sendRequest(chainCertUri, getSession());
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
conn.sendRequest(chainCertUri, getSession());
conn.accept(HttpURLConnection.HTTP_OK);
certChain.add(conn.readCertificate());
link = conn.getLink("up");
@ -188,10 +183,8 @@ public class Certificate extends AcmeResource {
claims.put("reason", reason.getReasonCode());
}
int rc = conn.sendSignedRequest(resUri, claims, getSession());
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
conn.sendSignedRequest(resUri, claims, getSession());
conn.accept(HttpURLConnection.HTTP_OK);
} catch (CertificateEncodingException ex) {
throw new AcmeProtocolException("Invalid certificate", ex);
}

View File

@ -158,10 +158,8 @@ public class Registration extends AcmeResource {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource("reg");
int rc = conn.sendSignedRequest(getLocation(), claims, getSession());
if (rc != HttpURLConnection.HTTP_CREATED && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getLocation(), claims, getSession());
conn.accept(HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED);
Map<String, Object> json = conn.readJsonResponse();
unmarshal(json, conn);
@ -190,10 +188,8 @@ public class Registration extends AcmeResource {
.put("type", "dns")
.put("value", DomainUtils.toAce(domain));
int rc = conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_AUTHZ), claims, getSession());
if (rc != HttpURLConnection.HTTP_CREATED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_AUTHZ), claims, getSession());
conn.accept(HttpURLConnection.HTTP_CREATED);
Map<String, Object> json = conn.readJsonResponse();
@ -249,10 +245,8 @@ public class Registration extends AcmeResource {
claims.put("notAfter", notAfter);
}
int rc = conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_CERT), claims, getSession());
if (rc != HttpURLConnection.HTTP_CREATED && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_CERT), claims, getSession());
int rc = conn.accept(HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED);
X509Certificate cert = null;
if (rc == HttpURLConnection.HTTP_CREATED) {
@ -307,10 +301,8 @@ public class Registration extends AcmeResource {
outerClaim.put("signature", innerJws.getEncodedSignature());
outerClaim.put("payload", innerJws.getEncodedPayload());
int rc = conn.sendSignedRequest(keyChangeUri, outerClaim, getSession());
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
conn.sendSignedRequest(keyChangeUri, outerClaim, getSession());
conn.accept(HttpURLConnection.HTTP_OK);
getSession().setKeyPair(newKeyPair);
} catch (JoseException ex) {
@ -331,10 +323,8 @@ public class Registration extends AcmeResource {
claims.putResource("reg");
claims.put("status", "deactivated");
int rc = conn.sendSignedRequest(getLocation(), claims, getSession());
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getLocation(), claims, getSession());
conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
}
}
@ -498,10 +488,8 @@ public class Registration extends AcmeResource {
claims.put("agreement", editAgreement);
}
int rc = conn.sendSignedRequest(getLocation(), claims, getSession());
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getLocation(), claims, getSession());
conn.accept(HttpURLConnection.HTTP_ACCEPTED);
Map<String, Object> json = conn.readJsonResponse();
unmarshal(json, conn);

View File

@ -81,10 +81,8 @@ public class RegistrationBuilder {
claims.put("contact", contacts);
}
int rc = conn.sendSignedRequest(session.resourceUri(Resource.NEW_REG), claims, session);
if (rc != HttpURLConnection.HTTP_CREATED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(session.resourceUri(Resource.NEW_REG), claims, session);
conn.accept(HttpURLConnection.HTTP_CREATED);
URI location = conn.getLocation();
URI tos = conn.getLink("terms-of-service");

View File

@ -76,10 +76,8 @@ public class Challenge extends AcmeResource {
LOG.debug("bind");
try (Connection conn = session.provider().connect()) {
int rc = conn.sendRequest(location, session);
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendRequest(location, session);
conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
Map<String, Object> json = conn.readJsonResponse();
if (!(json.containsKey("type"))) {
@ -211,10 +209,8 @@ public class Challenge extends AcmeResource {
claims.putResource("challenge");
respond(claims);
int rc = conn.sendSignedRequest(getLocation(), claims, getSession());
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendSignedRequest(getLocation(), claims, getSession());
conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
unmarshall(conn.readJsonResponse());
}
@ -233,10 +229,8 @@ public class Challenge extends AcmeResource {
public void update() throws AcmeException {
LOG.debug("update");
try (Connection conn = getSession().provider().connect()) {
int rc = conn.sendRequest(getLocation(), getSession());
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
conn.sendRequest(getLocation(), getSession());
int rc = conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
unmarshall(conn.readJsonResponse());

View File

@ -21,7 +21,6 @@ import java.util.Map;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.util.ClaimBuilder;
/**
@ -36,9 +35,8 @@ public interface Connection extends AutoCloseable {
* {@link URI} to send the request to.
* @param session
* {@link Session} instance to be used for tracking
* @return HTTP response code
*/
int sendRequest(URI uri, Session session) throws AcmeException;
void sendRequest(URI uri, Session session) throws AcmeException;
/**
* Sends a signed POST request.
@ -49,9 +47,18 @@ public interface Connection extends AutoCloseable {
* {@link ClaimBuilder} containing claims. Must not be {@code null}.
* @param session
* {@link Session} instance to be used for signing and tracking
* @return HTTP response code
*/
int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException;
void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException;
/**
* Checks if the HTTP response status is in the given list of acceptable HTTP states,
* otherwise raises an {@link AcmeException} matching the error.
*
* @param httpStatus
* Acceptable HTTP states
* @return Actual HTTP status that was accepted
*/
int accept(int... httpStatus) throws AcmeException;
/**
* Reads a server response as JSON data.
@ -114,13 +121,6 @@ public interface Connection extends AutoCloseable {
*/
Date getRetryAfterHeader();
/**
* Handles a problem by throwing an exception. If a JSON problem was returned, an
* {@link AcmeServerException} will be thrown. Otherwise a generic
* {@link AcmeException} is thrown.
*/
void throwAcmeException() throws AcmeException;
/**
* Closes the {@link Connection}, releasing all resources.
*/

View File

@ -79,7 +79,7 @@ public class DefaultConnection implements Connection {
}
@Override
public int sendRequest(URI uri, Session session) throws AcmeException {
public void sendRequest(URI uri, Session session) throws AcmeException {
if (uri == null) {
throw new NullPointerException("uri must not be null");
}
@ -100,15 +100,13 @@ public class DefaultConnection implements Connection {
conn.connect();
logHeaders();
return conn.getResponseCode();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException {
if (uri == null) {
throw new NullPointerException("uri must not be null");
}
@ -168,8 +166,6 @@ public class DefaultConnection implements Connection {
logHeaders();
updateSession(session);
return conn.getResponseCode();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} catch (JoseException ex) {
@ -177,6 +173,29 @@ public class DefaultConnection implements Connection {
}
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertConnectionIsOpen();
try {
int rc = conn.getResponseCode();
for (int s : httpStatus) {
if (s == rc) {
return rc;
}
}
if (!"application/problem+json".equals(conn.getHeaderField("Content-Type"))) {
throw new AcmeException("HTTP " + rc + ": " + conn.getResponseMessage());
}
Map<String, Object> map = readJsonResponse();
throw createAcmeException(rc, map);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@Override
public Map<String, Object> readJsonResponse() throws AcmeException {
assertConnectionIsOpen();
@ -334,15 +353,16 @@ public class DefaultConnection implements Connection {
}
@Override
public void throwAcmeException() throws AcmeException {
assertConnectionIsOpen();
if (!"application/problem+json".equals(conn.getHeaderField("Content-Type"))) {
throw new AcmeException("HTTP " + getResponseCode() + ": " + getResponseMessage());
public void close() {
conn = null;
}
Map<String, Object> map = readJsonResponse();
/**
* Handles a problem by throwing an exception. If a JSON problem was returned, an
* {@link AcmeServerException} will be thrown. Otherwise a generic
* {@link AcmeException} is thrown.
*/
private AcmeException createAcmeException(int rc, Map<String, Object> map) {
String type = (String) map.get("type");
String detail = (String) map.get("detail");
@ -350,41 +370,36 @@ public class DefaultConnection implements Connection {
detail = "general problem";
}
if (getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {
throw new AcmeConflictException(detail, getLocation());
if (rc == HttpURLConnection.HTTP_CONFLICT) {
return new AcmeConflictException(detail, getLocation());
}
if (type == null) {
throw new AcmeException(detail);
return new AcmeException(detail);
}
switch (type) {
case ACME_ERROR_PREFIX + "unauthorized":
case ACME_ERROR_PREFIX_DEPRECATED + "unauthorized":
throw new AcmeUnauthorizedException(type, detail);
return new AcmeUnauthorizedException(type, detail);
case ACME_ERROR_PREFIX + "agreementRequired":
case ACME_ERROR_PREFIX_DEPRECATED + "agreementRequired":
String instance = (String) map.get("instance");
throw new AcmeAgreementRequiredException(
return new AcmeAgreementRequiredException(
type, detail, getLink("terms-of-service"),
instance != null ? resolveRelative(instance) : null);
case ACME_ERROR_PREFIX + "rateLimited":
case ACME_ERROR_PREFIX_DEPRECATED + "rateLimited":
throw new AcmeRateLimitExceededException(
return new AcmeRateLimitExceededException(
type, detail, getRetryAfterHeader(), getLinks("rate-limit"));
default:
throw new AcmeServerException(type, detail);
return new AcmeServerException(type, detail);
}
}
@Override
public void close() {
conn = null;
}
/**
* Asserts that the connection is currently open. Throws an exception if not.
*/
@ -403,28 +418,6 @@ public class DefaultConnection implements Connection {
}
}
/**
* Returns the last response code.
*/
private int getResponseCode() throws AcmeException {
try {
return conn.getResponseCode();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
/**
* Returns the last response message.
*/
private String getResponseMessage() throws AcmeException {
try {
return conn.getResponseMessage();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
/**
* Log all HTTP headers in debug mode.
*/

View File

@ -144,10 +144,8 @@ public abstract class ResourceIterator<T extends AcmeResource> implements Iterat
*/
private void readAndQueue() throws AcmeException {
try (Connection conn = session.provider().connect()) {
int rc = conn.sendRequest(nextUri, session);
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
conn.sendRequest(nextUri, session);
conn.accept(HttpURLConnection.HTTP_OK);
Map<String, Object> json = conn.readJsonResponse();
fillUriList(json);

View File

@ -45,10 +45,8 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
@Override
public Map<String, Object> directory(Session session, URI serverUri) throws AcmeException {
try (Connection conn = connect()) {
int rc = conn.sendRequest(resolve(serverUri), session);
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
conn.sendRequest(resolve(serverUri), session);
conn.accept(HttpURLConnection.HTTP_OK);
// use nonce header if there is one, saves a HEAD request...
conn.updateSession(session);

View File

@ -15,7 +15,7 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.shredzone.acme4j.util.TestUtils.getJsonAsMap;
import static org.shredzone.acme4j.util.TestUtils.*;
import java.io.IOException;
import java.net.HttpURLConnection;
@ -30,6 +30,7 @@ import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.challenge.Dns01Challenge;
import org.shredzone.acme4j.challenge.Http01Challenge;
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -119,8 +120,14 @@ public class AuthorizationTest {
public void testUpdate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_OK;
}
@ -166,9 +173,15 @@ public class AuthorizationTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
requestWasSent.set(true);
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_OK;
}
@ -209,8 +222,14 @@ public class AuthorizationTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -265,12 +284,18 @@ public class AuthorizationTest {
public void testDeactivate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
Map<String, Object> claimMap = claims.toMap();
assertThat(claimMap.get("resource"), is((Object) "authz"));
assertThat(claimMap.get("status"), is((Object) "deactivated"));
assertThat(uri, is(locationUri));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
};

View File

@ -15,7 +15,7 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.shredzone.acme4j.util.TestUtils.getJson;
import static org.shredzone.acme4j.util.TestUtils.*;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException;
@ -52,9 +52,22 @@ public class CertificateTest {
private boolean isLocationUri;
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, isOneOf(locationUri, chainUri));
isLocationUri = uri.equals(locationUri);
}
@Override
public int accept(int... httpStatus) throws AcmeException {
if (isLocationUri) {
// The leaf certificate, might be asynchronous
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
} else {
// The root certificate chain, always OK
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK));
}
return HttpURLConnection.HTTP_OK;
}
@ -93,8 +106,14 @@ public class CertificateTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -125,10 +144,15 @@ public class CertificateTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateRequest")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}
};
@ -150,10 +174,15 @@ public class CertificateTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateWithReasonRequest")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}
};

View File

@ -15,7 +15,7 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.shredzone.acme4j.util.TestUtils.getJson;
import static org.shredzone.acme4j.util.TestUtils.*;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.net.HttpURLConnection;
@ -23,6 +23,7 @@ import java.net.URI;
import org.junit.Test;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -42,10 +43,15 @@ public class RegistrationBuilderTest {
public void testRegistration() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("newRegistration")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_CREATED));
return HttpURLConnection.HTTP_CREATED;
}

View File

@ -62,33 +62,42 @@ public class RegistrationTest {
public void testUpdateRegistration() throws AcmeException, IOException {
TestableConnectionProvider provider = new TestableConnectionProvider() {
private Map<String, Object> jsonResponse;
private Integer response;
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(locationUri));
assertThat(claims.toString(), sameJSONAs(getJson("updateRegistration")));
assertThat(session, is(notNullValue()));
jsonResponse = getJsonAsMap("updateRegistrationResponse");
return HttpURLConnection.HTTP_ACCEPTED;
response = HttpURLConnection.HTTP_ACCEPTED;
}
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
if (URI.create("https://example.com/acme/reg/1/authz").equals(uri)) {
jsonResponse = new HashMap<>();
jsonResponse.put("authorizations",
Arrays.asList("https://example.com/acme/auth/1"));
return HttpURLConnection.HTTP_OK;
response = HttpURLConnection.HTTP_OK;
return;
}
if (URI.create("https://example.com/acme/reg/1/cert").equals(uri)) {
jsonResponse = new HashMap<>();
jsonResponse.put("certificates",
Arrays.asList("https://example.com/acme/cert/1"));
return HttpURLConnection.HTTP_OK;
response = HttpURLConnection.HTTP_OK;
return;
}
return HttpURLConnection.HTTP_NOT_FOUND;
response = HttpURLConnection.HTTP_NOT_FOUND;
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(response, not(nullValue()));
return response;
}
@Override
@ -144,9 +153,15 @@ public class RegistrationTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
requestWasSent.set(true);
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -192,10 +207,15 @@ public class RegistrationTest {
public void testAuthorizeDomain() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("newAuthorizationRequest")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_CREATED));
return HttpURLConnection.HTTP_CREATED;
}
@ -250,16 +270,21 @@ public class RegistrationTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
fail("Attempted to download the certificate. Should be downloaded already!");
return HttpURLConnection.HTTP_OK;
}
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("requestCertificateRequestWithDate")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_CREATED;
}
@ -309,10 +334,16 @@ public class RegistrationTest {
public void testRequestCertificateAsync() throws AcmeException, IOException {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("requestCertificateRequest")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -353,7 +384,7 @@ public class RegistrationTest {
final TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder payload, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder payload, Session session) {
try {
assertThat(uri, is(locationUri));
assertThat(session, is(notNullValue()));
@ -386,7 +417,11 @@ public class RegistrationTest {
} catch (JoseException ex) {
fail("decoding inner payload failed");
}
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}
@ -434,12 +469,18 @@ public class RegistrationTest {
public void testDeactivate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
Map<String, Object> claimMap = claims.toMap();
assertThat(claimMap.get("resource"), is((Object) "reg"));
assertThat(claimMap.get("status"), is((Object) "deactivated"));
assertThat(uri, is(locationUri));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
};
@ -459,10 +500,15 @@ public class RegistrationTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(locationUri));
assertThat(claims.toString(), sameJSONAs(getJson("modifyRegistration")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}

View File

@ -35,6 +35,7 @@ import org.junit.Before;
import org.junit.Test;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.provider.TestableConnectionProvider;
@ -62,8 +63,14 @@ public class ChallengeTest {
public void testChallenge() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -142,10 +149,16 @@ public class ChallengeTest {
public void testTrigger() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("triggerHttpChallengeRequest")));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -175,8 +188,14 @@ public class ChallengeTest {
public void testUpdate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_OK;
}
@ -208,8 +227,14 @@ public class ChallengeTest {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
assertThat(uri, is(locationUri));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}

View File

@ -308,7 +308,24 @@ public class DefaultConnectionTest {
* Test if an {@link AcmeServerException} is thrown on an acme problem.
*/
@Test
public void testThrowException() throws Exception {
public void testAccept() throws Exception {
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.conn = mockUrlConnection;
int rc = conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
assertThat(rc, is(HttpURLConnection.HTTP_OK));
}
verify(mockUrlConnection).getResponseCode();
verifyNoMoreInteractions(mockUrlConnection);
}
/**
* Test if an {@link AcmeServerException} is thrown on an acme problem.
*/
@Test
public void testAcceptThrowsException() throws Exception {
String jsonData = "{\"type\":\"urn:ietf:params:acme:error:unauthorized\",\"detail\":\"Invalid response: 404\"}";
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/problem+json");
@ -317,7 +334,7 @@ public class DefaultConnectionTest {
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.conn = mockUrlConnection;
conn.throwAcmeException();
conn.accept(HttpURLConnection.HTTP_OK);
fail("Expected to fail");
} catch (AcmeServerException ex) {
assertThat(ex.getType(), is("urn:ietf:params:acme:error:unauthorized"));
@ -337,7 +354,7 @@ public class DefaultConnectionTest {
* Test if an {@link AcmeServerException} is thrown on another problem.
*/
@Test
public void testOtherThrowException() throws IOException {
public void testAcceptThrowsOtherException() throws IOException {
when(mockUrlConnection.getHeaderField("Content-Type"))
.thenReturn("application/problem+json");
when(mockUrlConnection.getResponseCode())
@ -353,7 +370,7 @@ public class DefaultConnectionTest {
};
}) {
conn.conn = mockUrlConnection;
conn.throwAcmeException();
conn.accept(HttpURLConnection.HTTP_OK);
fail("Expected to fail");
} catch (AcmeServerException ex) {
assertThat(ex.getType(), is("urn:zombie:error:apocalypse"));
@ -372,7 +389,7 @@ public class DefaultConnectionTest {
* Test if an {@link AcmeException} is thrown if there is no error type.
*/
@Test
public void testNoTypeThrowException() throws IOException {
public void testAcceptThrowsNoTypeException() throws IOException {
when(mockUrlConnection.getHeaderField("Content-Type"))
.thenReturn("application/problem+json");
when(mockUrlConnection.getResponseCode())
@ -385,7 +402,7 @@ public class DefaultConnectionTest {
};
}) {
conn.conn = mockUrlConnection;
conn.throwAcmeException();
conn.accept(HttpURLConnection.HTTP_OK);
fail("Expected to fail");
} catch (AcmeNetworkException ex) {
fail("Did not expect an AcmeNetworkException");
@ -412,7 +429,6 @@ public class DefaultConnectionTest {
verify(mockUrlConnection).setRequestProperty("Accept-Language", "ja-JP");
verify(mockUrlConnection).setDoOutput(false);
verify(mockUrlConnection).connect();
verify(mockUrlConnection).getResponseCode();
verify(mockUrlConnection, atLeast(0)).getHeaderFields();
verifyNoMoreInteractions(mockUrlConnection);
}
@ -457,7 +473,6 @@ public class DefaultConnectionTest {
verify(mockUrlConnection).setDoOutput(true);
verify(mockUrlConnection).setFixedLengthStreamingMode(outputStream.toByteArray().length);
verify(mockUrlConnection).getOutputStream();
verify(mockUrlConnection).getResponseCode();
verify(mockUrlConnection, atLeast(0)).getHeaderFields();
verifyNoMoreInteractions(mockUrlConnection);

View File

@ -30,12 +30,17 @@ import org.shredzone.acme4j.util.ClaimBuilder;
public class DummyConnection implements Connection {
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
throw new UnsupportedOperationException();
}
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
public void sendSignedRequest(URI uri, ClaimBuilder claims, Session session) {
throw new UnsupportedOperationException();
}
@Override
public int accept(int... httpStatus) throws AcmeException {
throw new UnsupportedOperationException();
}
@ -74,11 +79,6 @@ public class DummyConnection implements Connection {
throw new UnsupportedOperationException();
}
@Override
public void throwAcmeException() throws AcmeException {
throw new UnsupportedOperationException();
}
@Override
public void close() {
// closing is always safe

View File

@ -15,6 +15,7 @@ package org.shredzone.acme4j.connector;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.shredzone.acme4j.util.TestUtils.isIntArrayContainingInAnyOrder;
import java.io.IOException;
import java.net.HttpURLConnection;
@ -31,6 +32,7 @@ import org.junit.Before;
import org.junit.Test;
import org.shredzone.acme4j.Authorization;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -133,9 +135,14 @@ public class ResourceIteratorTest {
private int ix;
@Override
public int sendRequest(URI uri, Session session) {
public void sendRequest(URI uri, Session session) {
ix = pageURIs.indexOf(uri);
assertThat(ix, is(greaterThanOrEqualTo(0)));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}

View File

@ -83,7 +83,7 @@ public class AbstractAcmeProviderTest {
final Connection connection = mock(Connection.class);
final Session session = mock(Session.class);
when(connection.sendRequest(testResolvedUri, session)).thenReturn(HttpURLConnection.HTTP_OK);
when(connection.accept(any(Integer.class))).thenReturn(HttpURLConnection.HTTP_OK);
when(connection.readJsonResponse()).thenReturn(TestUtils.getJsonAsMap("directory"));
AbstractAcmeProvider provider = new AbstractAcmeProvider() {
@ -109,6 +109,7 @@ public class AbstractAcmeProviderTest {
assertThat(JsonUtil.toJson(map), sameJSONAs(TestUtils.getJson("directory")));
verify(connection).sendRequest(testResolvedUri, session);
verify(connection).accept(any(Integer.class));
verify(connection).updateSession(any(Session.class));
verify(connection).readJsonResponse();
verify(connection).close();

View File

@ -34,10 +34,14 @@ import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.JsonWebKey;
@ -224,6 +228,36 @@ public final class TestUtils {
}
}
/**
* Creates a matcher that matches an array of int primitives. The array must contain
* exactly all of the given values, in any order.
*
* @param values
* Values to test against
* @return {@link Matcher}
*/
public static Matcher<int[]> isIntArrayContainingInAnyOrder(int... values) {
final int[] reference = values;
Arrays.sort(reference);
return new BaseMatcher<int[]>() {
@Override
public boolean matches(Object item) {
if (!(item instanceof int[])) {
return false;
}
int[] items = (int[]) item;
Arrays.sort(items);
return Arrays.equals(items, reference);
}
@Override
public void describeTo(Description description) {
description.appendValue(Arrays.toString(reference));
}
};
}
/**
* Generates a new keypair for unit tests, and return its N, E, KTY and THUMBPRINT
* parameters to be set in the {@link TestUtils} class.