Catch IOExceptions early

pull/30/head
Richard Körber 2016-12-16 01:17:15 +01:00
parent 25700d5c41
commit 96d46784c6
10 changed files with 113 additions and 115 deletions

View File

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.ArrayList;
@ -26,7 +25,6 @@ import java.util.Map;
import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -194,8 +192,6 @@ public class Authorization extends AcmeResource {
retryAfter);
}
}
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -213,8 +209,6 @@ public class Authorization extends AcmeResource {
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}

View File

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.security.cert.CertificateEncodingException;
@ -25,7 +24,6 @@ import java.util.List;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -106,8 +104,6 @@ public class Certificate extends AcmeResource {
chainCertUri = conn.getLink("up");
cert = conn.readCertificate();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
return cert;
@ -146,8 +142,6 @@ public class Certificate extends AcmeResource {
certChain.add(conn.readCertificate());
link = conn.getLink("up");
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
if (link != null) {
@ -200,8 +194,6 @@ public class Certificate extends AcmeResource {
}
} catch (CertificateEncodingException ex) {
throw new AcmeProtocolException("Invalid certificate", ex);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}

View File

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
@ -35,7 +34,6 @@ import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.connector.ResourceIterator;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -167,8 +165,6 @@ public class Registration extends AcmeResource {
Map<String, Object> json = conn.readJsonResponse();
unmarshal(json, conn);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -204,8 +200,6 @@ public class Registration extends AcmeResource {
Authorization auth = new Authorization(getSession(), conn.getLocation());
auth.unmarshalAuthorization(json);
return auth;
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -268,8 +262,6 @@ public class Registration extends AcmeResource {
URI chainCertUri = conn.getLink("up");
return new Certificate(getSession(), conn.getLocation(), chainCertUri, cert);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -321,8 +313,6 @@ public class Registration extends AcmeResource {
}
getSession().setKeyPair(newKeyPair);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} catch (JoseException ex) {
throw new AcmeProtocolException("Cannot sign key-change", ex);
}
@ -345,8 +335,6 @@ public class Registration extends AcmeResource {
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -517,8 +505,6 @@ public class Registration extends AcmeResource {
Map<String, Object> json = conn.readJsonResponse();
unmarshal(json, conn);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
}

View File

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.ArrayList;
@ -23,7 +22,6 @@ import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeConflictException;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -92,8 +90,6 @@ public class RegistrationBuilder {
URI tos = conn.getLink("terms-of-service");
return new Registration(session, location, tos);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}

View File

@ -29,7 +29,6 @@ import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -88,8 +87,6 @@ public class Challenge extends AcmeResource {
}
return (T) session.createChallenge(json);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -220,8 +217,6 @@ public class Challenge extends AcmeResource {
}
unmarshall(conn.readJsonResponse());
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -252,8 +247,6 @@ public class Challenge extends AcmeResource {
retryAfter);
}
}
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}

View File

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j.connector;
import java.io.IOException;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Collection;
@ -39,7 +38,7 @@ public interface Connection extends AutoCloseable {
* {@link Session} instance to be used for tracking
* @return HTTP response code
*/
int sendRequest(URI uri, Session session) throws IOException;
int sendRequest(URI uri, Session session) throws AcmeException;
/**
* Sends a signed POST request.
@ -52,21 +51,21 @@ public interface Connection extends AutoCloseable {
* {@link Session} instance to be used for signing and tracking
* @return HTTP response code
*/
int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws IOException;
int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException;
/**
* Reads a server response as JSON data.
*
* @return Map containing the parsed JSON data
*/
Map<String, Object> readJsonResponse() throws IOException;
Map<String, Object> readJsonResponse() throws AcmeException;
/**
* Reads a certificate.
*
* @return {@link X509Certificate} that was read.
*/
X509Certificate readCertificate() throws IOException;
X509Certificate readCertificate() throws AcmeException;
/**
* Updates a {@link Session} by evaluating the HTTP response header.
@ -120,7 +119,7 @@ public interface Connection extends AutoCloseable {
* {@link AcmeServerException} will be thrown. Otherwise a generic
* {@link AcmeException} is thrown.
*/
void throwAcmeException() throws AcmeException, IOException;
void throwAcmeException() throws AcmeException;
/**
* Closes the {@link Connection}, releasing all resources.

View File

@ -44,6 +44,7 @@ import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeAgreementRequiredException;
import org.shredzone.acme4j.exception.AcmeConflictException;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRateLimitExceededException;
import org.shredzone.acme4j.exception.AcmeServerException;
@ -78,7 +79,7 @@ public class DefaultConnection implements Connection {
}
@Override
public int sendRequest(URI uri, Session session) throws IOException {
public int sendRequest(URI uri, Session session) throws AcmeException {
if (uri == null) {
throw new NullPointerException("uri must not be null");
}
@ -89,21 +90,25 @@ public class DefaultConnection implements Connection {
LOG.debug("GET {}", uri);
conn = httpConnector.openConnection(uri);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag());
conn.setDoOutput(false);
try {
conn = httpConnector.openConnection(uri);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag());
conn.setDoOutput(false);
conn.connect();
conn.connect();
logHeaders();
logHeaders();
return conn.getResponseCode();
return conn.getResponseCode();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws IOException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException {
if (uri == null) {
throw new NullPointerException("uri must not be null");
}
@ -165,13 +170,15 @@ public class DefaultConnection implements Connection {
updateSession(session);
return conn.getResponseCode();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} catch (JoseException ex) {
throw new AcmeProtocolException("Failed to generate a JSON request", ex);
}
}
@Override
public Map<String, Object> readJsonResponse() throws IOException {
public Map<String, Object> readJsonResponse() throws AcmeException {
assertConnectionIsOpen();
String contentType = conn.getHeaderField("Content-Type");
@ -190,6 +197,8 @@ public class DefaultConnection implements Connection {
result = JsonUtil.parseJson(response);
LOG.debug("Result JSON: {}", response);
}
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} catch (JoseException ex) {
throw new AcmeProtocolException("Failed to parse response: " + response, ex);
}
@ -213,7 +222,7 @@ public class DefaultConnection implements Connection {
}
@Override
public X509Certificate readCertificate() throws IOException {
public X509Certificate readCertificate() throws AcmeException {
assertConnectionIsOpen();
String contentType = conn.getHeaderField("Content-Type");
@ -224,6 +233,8 @@ public class DefaultConnection implements Connection {
try (InputStream in = conn.getInputStream()) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(in);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} catch (CertificateException ex) {
throw new AcmeProtocolException("Failed to read certificate", ex);
}
@ -323,49 +334,49 @@ public class DefaultConnection implements Connection {
}
@Override
public void throwAcmeException() throws AcmeException, IOException {
public void throwAcmeException() throws AcmeException {
assertConnectionIsOpen();
if ("application/problem+json".equals(conn.getHeaderField("Content-Type"))) {
Map<String, Object> map = readJsonResponse();
String type = (String) map.get("type");
String detail = (String) map.get("detail");
if (!"application/problem+json".equals(conn.getHeaderField("Content-Type"))) {
throw new AcmeException("HTTP " + getResponseCode() + ": " + getResponseMessage());
}
if (detail == null) {
detail = "general problem";
}
Map<String, Object> map = readJsonResponse();
if (conn.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {
throw new AcmeConflictException(detail, getLocation());
}
String type = (String) map.get("type");
String detail = (String) map.get("detail");
if (type == null) {
throw new AcmeException(detail);
}
if (detail == null) {
detail = "general problem";
}
switch (type) {
case ACME_ERROR_PREFIX + "unauthorized":
case ACME_ERROR_PREFIX_DEPRECATED + "unauthorized":
throw new AcmeUnauthorizedException(type, detail);
if (getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {
throw new AcmeConflictException(detail, getLocation());
}
case ACME_ERROR_PREFIX + "agreementRequired":
case ACME_ERROR_PREFIX_DEPRECATED + "agreementRequired":
String instance = (String) map.get("instance");
throw new AcmeAgreementRequiredException(
type, detail, getLink("terms-of-service"),
(instance != null ? resolveRelative(instance) : null));
if (type == null) {
throw new AcmeException(detail);
}
case ACME_ERROR_PREFIX + "rateLimited":
case ACME_ERROR_PREFIX_DEPRECATED + "rateLimited":
throw new AcmeRateLimitExceededException(
type, detail, getRetryAfterHeader(), getLinks("rate-limit"));
switch (type) {
case ACME_ERROR_PREFIX + "unauthorized":
case ACME_ERROR_PREFIX_DEPRECATED + "unauthorized":
throw new AcmeUnauthorizedException(type, detail);
default:
throw new AcmeServerException(type, detail);
}
} else {
throw new AcmeException("HTTP " + conn.getResponseCode() + ": "
+ conn.getResponseMessage());
case ACME_ERROR_PREFIX + "agreementRequired":
case ACME_ERROR_PREFIX_DEPRECATED + "agreementRequired":
String instance = (String) map.get("instance");
throw 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(
type, detail, getRetryAfterHeader(), getLinks("rate-limit"));
default:
throw new AcmeServerException(type, detail);
}
}
@ -392,6 +403,28 @@ 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

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j.connector;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
@ -27,7 +26,6 @@ import java.util.NoSuchElementException;
import org.shredzone.acme4j.AcmeResource;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/**
@ -144,7 +142,6 @@ public abstract class ResourceIterator<T extends AcmeResource> implements Iterat
* Reads the next batch of URIs from the server, and fills the queue with the URIs. If
* there is a "next" header, it is used for the next batch of URIs.
*/
@SuppressWarnings("unchecked")
private void readAndQueue() throws AcmeException {
try (Connection conn = session.provider().connect()) {
int rc = conn.sendRequest(nextUri, session);
@ -153,22 +150,33 @@ public abstract class ResourceIterator<T extends AcmeResource> implements Iterat
}
Map<String, Object> json = conn.readJsonResponse();
try {
Collection<String> array = (Collection<String>) json.get(field);
if (array != null) {
for (String uri : array) {
uriList.add(new URI(uri));
}
}
} catch (ClassCastException ex) {
throw new AcmeProtocolException("Expected an array");
} catch (URISyntaxException ex) {
throw new AcmeProtocolException("Invalid URI", ex);
}
fillUriList(json);
nextUri = conn.getLink("next");
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
/**
* Fills the uri list with the URIs found in the desired field.
*
* @param json
* JSON map to read from
*/
private void fillUriList(Map<String, Object> json) {
try {
@SuppressWarnings("unchecked")
Collection<String> array = (Collection<String>) json.get(field);
if (array == null) {
return;
}
for (String uri : array) {
uriList.add(new URI(uri));
}
} catch (ClassCastException ex) {
throw new AcmeProtocolException("Expected an array", ex);
} catch (URISyntaxException ex) {
throw new AcmeProtocolException("Invalid URI", ex);
}
}

View File

@ -13,7 +13,6 @@
*/
package org.shredzone.acme4j.provider;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Map;
@ -28,7 +27,6 @@ import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.DefaultConnection;
import org.shredzone.acme4j.connector.HttpConnector;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
/**
* Abstract implementation of {@link AcmeProvider}. It consists of a challenge
@ -56,8 +54,6 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
conn.updateSession(session);
return conn.readJsonResponse();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}

View File

@ -41,6 +41,7 @@ import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -358,7 +359,7 @@ public class DefaultConnectionTest {
assertThat(ex.getType(), is("urn:zombie:error:apocalypse"));
assertThat(ex.getMessage(), is("Zombie apocalypse in progress"));
assertThat(ex.getAcmeErrorType(), is(nullValue()));
} catch (AcmeException | IOException ex) {
} catch (AcmeException ex) {
fail("Expected an AcmeServerException");
}
@ -386,10 +387,10 @@ public class DefaultConnectionTest {
conn.conn = mockUrlConnection;
conn.throwAcmeException();
fail("Expected to fail");
} catch (AcmeNetworkException ex) {
fail("Did not expect an AcmeNetworkException");
} catch (AcmeException ex) {
assertThat(ex.getMessage(), not(isEmptyOrNullString()));
} catch (IOException ex) {
fail("Expected an AcmeException");
}
verify(mockUrlConnection).getHeaderField("Content-Type");