Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include HTTP response body in thrown exception #68

Merged
merged 1 commit into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 70 additions & 22 deletions src/main/java/com/versionone/apiclient/V1Connector.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.versionone.apiclient;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
Expand All @@ -24,13 +23,14 @@
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
Expand All @@ -41,16 +41,13 @@

import com.versionone.apiclient.exceptions.ConnectionException;
import com.versionone.apiclient.exceptions.V1Exception;
import com.versionone.apiclient.interfaces.IAttributeDefinition;
import com.versionone.utils.V1Util;

import wiremock.com.google.common.io.CharStreams;

public class V1Connector {

private final String contentType = "text/xml";
private CredentialsProvider credsProvider = new BasicCredentialsProvider();
private CloseableHttpResponse httpResponse = null;
private HttpResponse httpResponse = null;
private HttpClientBuilder httpclientBuilder = HttpClientBuilder.create();
private CloseableHttpClient httpclient;
private Header[] headerArray = {};
Expand Down Expand Up @@ -86,7 +83,13 @@ public interface IsetEndpoint {
*/
@Deprecated
IsetProxyOrConnector useEndpoint(String endpoint);


/**
* Method for specifying client for testing purposes.
* @param httpClient client to use
* @return next builder stage
*/
IBuild withHttpClient(HttpClientBuilder httpClient);
}

public interface IsetProxyOrConnector extends IBuild {
Expand Down Expand Up @@ -286,6 +289,12 @@ public IsetEndPointOrConnector withProxy(ProxyProvider proxyProvider) {
return this;
}

@Override
public IBuild withHttpClient(HttpClientBuilder httpClient) {
v1Connector_instance.httpclientBuilder = httpClient;
return this;
}

@Override
public IsetProxyOrConnector useEndpoint(String endpoint) {

Expand Down Expand Up @@ -313,7 +322,6 @@ protected Reader getData(String path) throws ConnectionException {
Reader data = null;
HttpEntity entity = setGETMethod(path);
int errorCode = httpResponse.getStatusLine().getStatusCode();
String errorMessage = "\n" + httpResponse.getStatusLine() + " error code: " + errorCode;

if (errorCode == HttpStatus.SC_OK) {
try {
Expand All @@ -324,7 +332,7 @@ protected Reader getData(String path) throws ConnectionException {
e.printStackTrace();
}
} else {
manageErrors( errorCode, errorMessage);
manageErrors(httpResponse);
}
return data;
}
Expand All @@ -333,7 +341,6 @@ protected InputStream getAttachment(String path) throws ConnectionException {
InputStream data = null;
HttpEntity entity = setGETMethod(path);
int errorCode = httpResponse.getStatusLine().getStatusCode();
String errorMessage = "\n" + httpResponse.getStatusLine() + " error code: " + errorCode;

if (errorCode == HttpStatus.SC_OK) {
try {
Expand All @@ -344,7 +351,7 @@ protected InputStream getAttachment(String path) throws ConnectionException {
e.printStackTrace();
}
} else {
manageErrors( errorCode, errorMessage);
manageErrors(httpResponse);
}
return data;
}
Expand Down Expand Up @@ -380,27 +387,33 @@ private HttpEntity setGETMethod(String path) {
return entity;
}

private void manageErrors(int errorCode, String errorMessage) throws ConnectionException {

switch (errorCode) {
private void manageErrors(HttpResponse httpResponse) throws ConnectionException {
String errorMessage = httpResponse.getStatusLine().toString() + ":";
String responseString = readResponseStringPushback(httpResponse);
String suffixString = responseString == null ? "" : " Response: " + responseString;

switch (httpResponse.getStatusLine().getStatusCode()) {
case HttpStatus.SC_BAD_REQUEST:
throw new ConnectionException(errorMessage + " VersionOne could not process the request.");
throw new ConnectionException(errorMessage + " VersionOne could not process the request." + suffixString);

case HttpStatus.SC_UNAUTHORIZED:
throw new ConnectionException(errorMessage
+ " Could not authenticate. The VersionOne credentials may be incorrect or the access tokens may have expired.");
throw new ConnectionException(errorMessage + " Could not authenticate. The VersionOne credentials may be " +
"incorrect or the access tokens may have expired." + suffixString);

case HttpStatus.SC_NOT_FOUND:
throw new ConnectionException(errorMessage + " The requested item may not exist, or the VersionOne server is unavailable.");
throw new ConnectionException(errorMessage + " The requested item may not exist, or the VersionOne " +
"server is unavailable." + suffixString);

case HttpStatus.SC_METHOD_NOT_ALLOWED:
throw new ConnectionException(errorMessage + " Only GET and POST methods are supported by VersionOne.");
throw new ConnectionException(errorMessage + " Only GET and POST methods are supported by VersionOne." +
suffixString);

case HttpStatus.SC_INTERNAL_SERVER_ERROR:
throw new ConnectionException(errorMessage + " VersionOne encountered an unexpected error occurred while processing the request.");
throw new ConnectionException(errorMessage + " VersionOne encountered an unexpected error occurred while " +
"processing the request." + suffixString);

default:
throw new ConnectionException(errorMessage);
throw new ConnectionException(errorMessage + suffixString);
}
}

Expand Down Expand Up @@ -532,6 +545,41 @@ protected InputStream endRequest(String path) throws ConnectionException {
return null;
}

private static String readResponseStringPushback(HttpResponse httpResponse) {
HttpEntity responseEntity = httpResponse.getEntity();
if (responseEntity == null) {
return null;
}
byte[] contentBytes;
try {
contentBytes = IOUtils.toByteArray(responseEntity.getContent());
} catch (Exception contentException) {
return null;
}
// put back response so it can be re-read again
httpResponse.setEntity(copyEntity(responseEntity, contentBytes));
try {
return new String(contentBytes, StandardCharsets.UTF_8);
} catch (Exception ignored) {
// ignore encoding exception
}
try {
return new String(contentBytes, StandardCharsets.ISO_8859_1);
} catch (Exception ignored) {
// ignore encoding exception
}
return null;
}

private static BasicHttpEntity copyEntity(HttpEntity responseEntity, byte[] contentBytes) {
BasicHttpEntity entityCopy = new BasicHttpEntity();
entityCopy.setContent(new ByteArrayInputStream(contentBytes));
entityCopy.setContentLength(responseEntity.getContentLength());
entityCopy.setContentEncoding(responseEntity.getContentEncoding());
entityCopy.setContentType(responseEntity.getContentType());
return entityCopy;
}

// endpoint definition
public void useMetaAPI() {
_endpoint = META_API_ENDPOINT;
Expand Down
69 changes: 69 additions & 0 deletions src/test/java/com/versionone/sdk/unit/tests/ManageErrorTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.versionone.sdk.unit.tests;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestExecutor;
import org.junit.Assert;
import org.junit.Test;

import com.versionone.apiclient.V1Connector;
import com.versionone.apiclient.exceptions.V1Exception;

public class ManageErrorTests {
public static final ProtocolVersion HTTP_11 = new ProtocolVersion("HTTP", 1, 1);

@Test
public void testReturn404() throws V1Exception, IOException {
String inputText = "Not Found";
V1Connector connector = createV1ConnectorStub(new BasicStatusLine(HTTP_11, 404, "Not Found"),
inputText.getBytes(StandardCharsets.UTF_8), "text/plain");
Reader response = connector.sendData("test-key", "empty-data", "application/octet-stream");
Assert.assertTrue(IOUtils.toString(response).contains(inputText));
}

@Test
public void testReturn500() throws V1Exception, IOException {
String inputText = "Internal Server Error";
V1Connector connector = createV1ConnectorStub(new BasicStatusLine(HTTP_11, 500, "Internal Server Error"),
inputText.getBytes(StandardCharsets.UTF_8), "text/plain");
Reader response = connector.sendData("test-key", "empty-data", "application/octet-stream");
Assert.assertTrue(IOUtils.toString(response).contains(inputText));
}

@SuppressWarnings("SameParameterValue")
private static V1Connector createV1ConnectorStub(final BasicStatusLine statusLine, final byte[] contentBytes,
final String contentType) throws V1Exception, MalformedURLException {
HttpRequestExecutor requestExecutor = new HttpRequestExecutor() {
@Override
public HttpResponse execute(HttpRequest request, HttpClientConnection conn, HttpContext context) {
BasicHttpResponse basicResponse = new BasicHttpResponse(statusLine);
BasicHttpEntity basicEntity = new BasicHttpEntity();
basicEntity.setContent(new ByteArrayInputStream(contentBytes));
basicEntity.setContentType(contentType);
basicResponse.setEntity(basicEntity);
return basicResponse;
}
};
HttpClientBuilder mockHttpClient = HttpClientBuilder.create()
.setRequestExecutor(requestExecutor);
return V1Connector.withInstanceUrl("http://localhost:80")
.withUserAgentHeader("Test-User-Agent", "1.0")
.withAccessToken("test-access-token")
.withHttpClient(mockHttpClient)
.build();
}
}