mirror of https://github.com/apache/jclouds.git
Added OkHttp driver to support modern HTTP verbs
This commit is contained in:
parent
35ecf794cf
commit
09a430744a
|
@ -76,7 +76,6 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp</groupId>
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
<artifactId>mockwebserver</artifactId>
|
<artifactId>mockwebserver</artifactId>
|
||||||
<version>1.2.1</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -82,6 +82,11 @@
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
|
|
|
@ -19,12 +19,12 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Throwables.propagate;
|
import static com.google.common.base.Throwables.propagate;
|
||||||
import static com.google.common.io.ByteStreams.toByteArray;
|
import static com.google.common.io.ByteStreams.toByteArray;
|
||||||
|
import static com.google.common.io.Closeables.close;
|
||||||
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
|
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
|
||||||
import static com.google.common.net.HttpHeaders.HOST;
|
import static com.google.common.net.HttpHeaders.HOST;
|
||||||
import static com.google.common.net.HttpHeaders.USER_AGENT;
|
import static com.google.common.net.HttpHeaders.USER_AGENT;
|
||||||
import static org.jclouds.http.HttpUtils.filterOutContentHeaders;
|
import static org.jclouds.http.HttpUtils.filterOutContentHeaders;
|
||||||
import static org.jclouds.io.Payloads.newInputStreamPayload;
|
import static org.jclouds.io.Payloads.newInputStreamPayload;
|
||||||
import static org.jclouds.util.Closeables2.closeQuietly;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -46,6 +46,7 @@ import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import org.jclouds.Constants;
|
import org.jclouds.Constants;
|
||||||
import org.jclouds.JcloudsVersion;
|
import org.jclouds.JcloudsVersion;
|
||||||
|
import org.jclouds.http.HttpCommandExecutorService;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.HttpUtils;
|
import org.jclouds.http.HttpUtils;
|
||||||
|
@ -75,11 +76,11 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
||||||
public static final String DEFAULT_USER_AGENT = String.format("jclouds/%s java/%s", JcloudsVersion.get(), System
|
public static final String DEFAULT_USER_AGENT = String.format("jclouds/%s java/%s", JcloudsVersion.get(), System
|
||||||
.getProperty("java.version"));
|
.getProperty("java.version"));
|
||||||
|
|
||||||
private final Supplier<SSLContext> untrustedSSLContextProvider;
|
protected final Supplier<SSLContext> untrustedSSLContextProvider;
|
||||||
private final Function<URI, Proxy> proxyForURI;
|
protected final Function<URI, Proxy> proxyForURI;
|
||||||
private final HostnameVerifier verifier;
|
protected final HostnameVerifier verifier;
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
Supplier<SSLContext> sslContextSupplier;
|
protected Supplier<SSLContext> sslContextSupplier;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public JavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
|
public JavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
|
||||||
|
@ -105,13 +106,13 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
in = bufferAndCloseStream(connection.getErrorStream());
|
in = bufferAndCloseStream(connection.getErrorStream());
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
closeQuietly(in);
|
close(in, true);
|
||||||
throw propagate(e);
|
throw propagate(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
if (responseCode == 204) {
|
if (responseCode == 204) {
|
||||||
closeQuietly(in);
|
close(in, true);
|
||||||
in = null;
|
in = null;
|
||||||
}
|
}
|
||||||
builder.statusCode(responseCode);
|
builder.statusCode(responseCode);
|
||||||
|
@ -141,7 +142,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
||||||
in = new ByteArrayInputStream(toByteArray(inputStream));
|
in = new ByteArrayInputStream(toByteArray(inputStream));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
closeQuietly(inputStream);
|
close(inputStream, true);
|
||||||
}
|
}
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
@ -149,21 +150,8 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
||||||
@Override
|
@Override
|
||||||
protected HttpURLConnection convert(HttpRequest request) throws IOException, InterruptedException {
|
protected HttpURLConnection convert(HttpRequest request) throws IOException, InterruptedException {
|
||||||
boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding"));
|
boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding"));
|
||||||
URL url = request.getEndpoint().toURL();
|
|
||||||
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxyForURI.apply(request.getEndpoint()));
|
HttpURLConnection connection = initConnection(request);
|
||||||
if (connection instanceof HttpsURLConnection) {
|
|
||||||
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
|
|
||||||
if (utils.relaxHostname())
|
|
||||||
sslCon.setHostnameVerifier(verifier);
|
|
||||||
if (sslContextSupplier != null) {
|
|
||||||
// used for providers which e.g. use certs for authentication (like FGCP)
|
|
||||||
// Provider provides SSLContext impl (which inits context with key manager)
|
|
||||||
sslCon.setSSLSocketFactory(sslContextSupplier.get().getSocketFactory());
|
|
||||||
} else if (utils.trustAllCerts()) {
|
|
||||||
sslCon.setSSLSocketFactory(untrustedSSLContextProvider.get().getSocketFactory());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.setConnectTimeout(utils.getConnectionTimeout());
|
connection.setConnectTimeout(utils.getConnectionTimeout());
|
||||||
connection.setReadTimeout(utils.getSocketOpenTimeout());
|
connection.setReadTimeout(utils.getSocketOpenTimeout());
|
||||||
connection.setAllowUserInteraction(false);
|
connection.setAllowUserInteraction(false);
|
||||||
|
@ -173,10 +161,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
||||||
connection.setInstanceFollowRedirects(false);
|
connection.setInstanceFollowRedirects(false);
|
||||||
|
|
||||||
setRequestMethodBypassingJREMethodLimitation(connection, request.getMethod());
|
setRequestMethodBypassingJREMethodLimitation(connection, request.getMethod());
|
||||||
|
configureRequestHeaders(connection, request);
|
||||||
for (Map.Entry<String, String> entry : request.getHeaders().entries()) {
|
|
||||||
connection.setRequestProperty(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
String host = request.getEndpoint().getHost();
|
String host = request.getEndpoint().getHost();
|
||||||
if (request.getEndpoint().getPort() != -1) {
|
if (request.getEndpoint().getPort() != -1) {
|
||||||
|
@ -216,6 +201,36 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and initializes the connection.
|
||||||
|
*/
|
||||||
|
protected HttpURLConnection initConnection(HttpRequest request) throws IOException {
|
||||||
|
URL url = request.getEndpoint().toURL();
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxyForURI.apply(request.getEndpoint()));
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
|
||||||
|
if (utils.relaxHostname())
|
||||||
|
sslCon.setHostnameVerifier(verifier);
|
||||||
|
if (sslContextSupplier != null) {
|
||||||
|
// used for providers which e.g. use certs for authentication (like FGCP)
|
||||||
|
// Provider provides SSLContext impl (which inits context with key manager)
|
||||||
|
sslCon.setSSLSocketFactory(sslContextSupplier.get().getSocketFactory());
|
||||||
|
} else if (utils.trustAllCerts()) {
|
||||||
|
sslCon.setSSLSocketFactory(untrustedSSLContextProvider.get().getSocketFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the HTTP request headers in the connection.
|
||||||
|
*/
|
||||||
|
protected void configureRequestHeaders(HttpURLConnection connection, HttpRequest request) {
|
||||||
|
for (Map.Entry<String, String> entry : request.getHeaders().entries()) {
|
||||||
|
connection.setRequestProperty(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workaround for a bug in <code>HttpURLConnection.setRequestMethod(String)</code>
|
* Workaround for a bug in <code>HttpURLConnection.setRequestMethod(String)</code>
|
||||||
* The implementation of Sun Microsystems is throwing a <code>ProtocolException</code>
|
* The implementation of Sun Microsystems is throwing a <code>ProtocolException</code>
|
||||||
|
|
|
@ -16,38 +16,37 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.http;
|
package org.jclouds.http;
|
||||||
|
|
||||||
|
import static com.google.common.io.Closeables.close;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.fail;
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.server.Request;
|
|
||||||
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
|
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
|
||||||
|
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the retry behavior of the default {@link RetryHandler} implementation
|
* Tests the retry behavior of the default {@link RetryHandler} implementation
|
||||||
* {@link BackoffLimitedRetryHandler} to ensure that retries up to the default limit succeed.
|
* {@link BackoffLimitedRetryHandler} to ensure that retries up to the default
|
||||||
*
|
* limit succeed.
|
||||||
* TODO: Should either explicitly set retry limit or get it from Guice, rather than assuming it's 5.
|
|
||||||
*
|
*
|
||||||
* @author James Murty
|
* @author James Murty
|
||||||
|
* @author Ignasi Barrera
|
||||||
*/
|
*/
|
||||||
@Test(sequential = true)
|
@Test(groups = "integration")
|
||||||
public class BackoffLimitedRetryJavaTest extends BaseJettyTest {
|
public class BackoffLimitedRetryJavaTest extends BaseMockWebServerTest {
|
||||||
private int beginToFailOnRequestNumber = 0;
|
|
||||||
private int endFailuresOnRequestNumber = 0;
|
private final int maxRetries = 5;
|
||||||
private int requestCount = 0;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addConnectionProperties(Properties props) {
|
protected void addOverrideProperties(Properties props) {
|
||||||
|
props.setProperty(PROPERTY_MAX_RETRIES, "" + maxRetries);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -55,84 +54,98 @@ public class BackoffLimitedRetryJavaTest extends BaseJettyTest {
|
||||||
return new JavaUrlHttpCommandExecutorServiceModule();
|
return new JavaUrlHttpCommandExecutorServiceModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected IntegrationTestClient client(String url) {
|
||||||
protected boolean failEveryTenRequests(HttpServletRequest request, HttpServletResponse response)
|
return api(IntegrationTestClient.class, url);
|
||||||
throws IOException {
|
|
||||||
requestCount++;
|
|
||||||
boolean shouldFail = requestCount >= beginToFailOnRequestNumber
|
|
||||||
&& requestCount <= endFailuresOnRequestNumber;
|
|
||||||
if (shouldFail) {
|
|
||||||
response.sendError(500);
|
|
||||||
((Request) request).setHandled(true);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String submitGetRequest() throws InterruptedException, ExecutionException {
|
|
||||||
return client.download("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoRetriesSuccessful() throws InterruptedException, ExecutionException {
|
public void testNoRetriesSuccessful() throws Exception {
|
||||||
beginToFailOnRequestNumber = 1;
|
MockWebServer server = mockWebServer(new MockResponse());
|
||||||
endFailuresOnRequestNumber = 1;
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
requestCount = 0;
|
try {
|
||||||
|
client.download("");
|
||||||
assertEquals(submitGetRequest().trim(), XML);
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSingleRetrySuccessful() throws InterruptedException, ExecutionException {
|
public void testSingleRetrySuccessful() throws Exception {
|
||||||
beginToFailOnRequestNumber = 0;
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500), new MockResponse());
|
||||||
endFailuresOnRequestNumber = 1;
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
requestCount = 0;
|
try {
|
||||||
|
client.download("");
|
||||||
assertEquals(submitGetRequest().trim(), XML);
|
assertEquals(server.getRequestCount(), 2);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMaximumRetriesSuccessful() throws InterruptedException, ExecutionException {
|
public void testMaximumRetriesSuccessful() throws Exception {
|
||||||
beginToFailOnRequestNumber = 0;
|
MockWebServer server = mockWebServer();
|
||||||
endFailuresOnRequestNumber = 5;
|
for (int i = 0; i < maxRetries - 1; i++) {
|
||||||
requestCount = 0;
|
server.enqueue(new MockResponse().setResponseCode(500));
|
||||||
|
}
|
||||||
|
server.enqueue(new MockResponse());
|
||||||
|
|
||||||
assertEquals(submitGetRequest().trim(), XML);
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
client.download("");
|
||||||
|
assertEquals(server.getRequestCount(), maxRetries);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMaximumRetriesExceeded() throws InterruptedException, ExecutionException {
|
public void testMaximumRetriesExceeded() throws Exception {
|
||||||
beginToFailOnRequestNumber = 0;
|
MockWebServer server = mockWebServer();
|
||||||
endFailuresOnRequestNumber = 6;
|
for (int i = 0; i <= maxRetries; i++) {
|
||||||
requestCount = 0;
|
server.enqueue(new MockResponse().setResponseCode(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
|
||||||
|
client.download("");
|
||||||
|
fail("Request should not succeed within " + maxRetries + " requests");
|
||||||
|
} catch (HttpResponseException ex) {
|
||||||
|
assertEquals(ex.getResponse().getStatusCode(), 500);
|
||||||
|
assertEquals(server.getRequestCount(), maxRetries + 1);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInterleavedSuccessesAndFailures() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse(), new MockResponse());
|
||||||
|
for (int i = 0; i <= maxRetries; i++) {
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
client.download("");
|
||||||
|
client.download("");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
submitGetRequest();
|
client.download("");
|
||||||
fail("Request should not succeed within " + endFailuresOnRequestNumber + " requests");
|
fail("Request should not succeed within " + maxRetries + " requests");
|
||||||
} catch (HttpResponseException e) {
|
} catch (HttpResponseException ex) {
|
||||||
assertEquals(e.getResponse().getStatusCode(), 500);
|
assertEquals(ex.getResponse().getStatusCode(), 500);
|
||||||
|
assertEquals(server.getRequestCount(), maxRetries + 3);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInterleavedSuccessesAndFailures() throws InterruptedException,
|
|
||||||
ExecutionException {
|
|
||||||
beginToFailOnRequestNumber = 3;
|
|
||||||
endFailuresOnRequestNumber = 3 + 5; // Force third request to fail completely
|
|
||||||
requestCount = 0;
|
|
||||||
|
|
||||||
assertEquals(submitGetRequest().trim(), XML);
|
|
||||||
assertEquals(submitGetRequest().trim(), XML);
|
|
||||||
|
|
||||||
try {
|
|
||||||
submitGetRequest();
|
|
||||||
fail("Third request should not succeed by attempt number " + requestCount);
|
|
||||||
} catch (HttpResponseException e) {
|
|
||||||
assertEquals(e.getResponse().getStatusCode(), 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(submitGetRequest().trim(), XML);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,72 +18,148 @@ package org.jclouds.http;
|
||||||
|
|
||||||
import static com.google.common.hash.Hashing.md5;
|
import static com.google.common.hash.Hashing.md5;
|
||||||
import static com.google.common.io.BaseEncoding.base64;
|
import static com.google.common.io.BaseEncoding.base64;
|
||||||
import static java.lang.String.format;
|
import static com.google.common.io.ByteStreams.join;
|
||||||
|
import static com.google.common.io.ByteStreams.newInputStreamSupplier;
|
||||||
|
import static com.google.common.io.ByteStreams.toByteArray;
|
||||||
|
import static com.google.common.io.Closeables.close;
|
||||||
|
import static com.google.common.io.Files.asByteSource;
|
||||||
import static org.jclouds.http.options.GetOptions.Builder.tail;
|
import static org.jclouds.http.options.GetOptions.Builder.tail;
|
||||||
import static org.jclouds.io.ByteSources.asByteSource;
|
import static org.jclouds.io.ByteSources.asByteSource;
|
||||||
import static org.jclouds.io.Payloads.newFilePayload;
|
import static org.jclouds.io.Payloads.newByteSourcePayload;
|
||||||
import static org.jclouds.io.Payloads.newStringPayload;
|
|
||||||
import static org.jclouds.util.Closeables2.closeQuietly;
|
|
||||||
import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
|
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertFalse;
|
||||||
import static org.testng.Assert.assertTrue;
|
import static org.testng.Assert.assertTrue;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.net.MalformedURLException;
|
import java.net.URLDecoder;
|
||||||
import java.net.URI;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.Random;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
import org.jclouds.io.Payload;
|
import org.jclouds.io.Payload;
|
||||||
import org.jclouds.util.Strings2;
|
import org.jclouds.util.Strings2;
|
||||||
|
import org.testng.annotations.BeforeClass;
|
||||||
import org.testng.annotations.DataProvider;
|
import org.testng.annotations.DataProvider;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.CharSink;
|
import com.google.common.io.CharSink;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
|
import com.google.common.io.InputSupplier;
|
||||||
|
import com.squareup.okhttp.mockwebserver.Dispatcher;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||||
|
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for functionality all {@link HttpCommandExecutorService http executor
|
* Tests for functionality all {@link HttpCommandExecutorService} http executor
|
||||||
* services} must express. These tests will operate against an in-memory http
|
* services must express. These tests will operate against an in-memory http
|
||||||
* engine, so as to ensure end-to-end functionality works.
|
* engine, so as to ensure end-to-end functionality works.
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
|
* @author Ignasi Barrera
|
||||||
*/
|
*/
|
||||||
@Test(threadPoolSize = 10, groups = "integration")
|
@Test(groups = "integration")
|
||||||
public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends BaseJettyTest {
|
public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends BaseMockWebServerTest {
|
||||||
|
|
||||||
@Test(invocationCount = 25, timeOut = 5000)
|
private static final String XML = "<foo><bar>whoppers</bar></foo>";
|
||||||
public void testRequestFilter() {
|
private static final String XML2 = "<foo><bar>chubbs</bar></foo>";
|
||||||
assertEquals(client.downloadFilter("", "filterme").trim(), "test");
|
|
||||||
|
private String constitutionsMd5;
|
||||||
|
private long constitutionsLength;
|
||||||
|
private InputSupplier<InputStream> oneHundredOneConstitutions;
|
||||||
|
|
||||||
|
@BeforeClass(groups = "integration")
|
||||||
|
public void setup() throws IOException {
|
||||||
|
oneHundredOneConstitutions = getTestDataSupplier();
|
||||||
|
constitutionsMd5 = base64().encode(asByteSource(oneHundredOneConstitutions.getInput()).hash(md5()).asBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
protected IntegrationTestClient client(String url) {
|
||||||
public void testGetStringWithHeader() {
|
return api(IntegrationTestClient.class, url);
|
||||||
assertEquals(client.download("", "test").trim(), "test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 1, timeOut = 5000)
|
@Test
|
||||||
public void testAlternateMethod() {
|
public void testRequestFilter() throws Exception {
|
||||||
assertEquals(client.rowdy("").trim(), XML);
|
MockWebServer server = mockWebServer(new MockResponse().setBody("test"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.downloadFilter("", "filterme");
|
||||||
|
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getHeader("filterme"), "filterme");
|
||||||
|
assertEquals(request.getHeader("test"), "test");
|
||||||
|
assertEquals(result, "test");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testGetString() {
|
public void testGetStringWithHeader() throws Exception {
|
||||||
assertEquals(client.download("").trim(), XML);
|
MockWebServer server = mockWebServer(new MockResponse().setBody("test"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.download("", "test");
|
||||||
|
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getHeader("test"), "test");
|
||||||
|
assertEquals(result, "test");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testGetStringViaRequest() throws IOException {
|
public void testGetString() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setBody(XML));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
assertEquals(client.download(""), XML);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetStringIsRetriedOnFailure() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500), new MockResponse().setBody(XML));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.download("");
|
||||||
|
assertEquals(server.getRequestCount(), 2);
|
||||||
|
assertEquals(result, XML);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetStringViaRequest() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setBody(XML));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
HttpResponse getStringResponse = client.invoke(HttpRequest.builder().method("GET")
|
HttpResponse getStringResponse = client.invoke(HttpRequest.builder().method("GET")
|
||||||
.endpoint(format("http://localhost:%d/objects/", testPort)).build());
|
.endpoint(server.getUrl("/objects").toString()).build());
|
||||||
assertEquals(Strings2.toString(getStringResponse.getPayload()).trim(), XML);
|
assertEquals(Strings2.toString(getStringResponse.getPayload()).trim(), XML);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataProvider(name = "gets")
|
@DataProvider(name = "gets")
|
||||||
|
@ -91,67 +167,122 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
|
||||||
return new Object[][] { { "object" }, { "/path" }, { "sp ace" }, { "unic₪de" }, { "qu?stion" } };
|
return new Object[][] { { "object" }, { "/path" }, { "sp ace" }, { "unic₪de" }, { "qu?stion" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000, dataProvider = "gets")
|
@Test(dataProvider = "gets")
|
||||||
public void testGetStringSynch(String uri) {
|
public void testGetStringSynch(String uri) throws Exception {
|
||||||
assertEquals(client.synch(uri).trim(), XML);
|
MockWebServer server = mockWebServer(new MockResponse().setBody(XML));
|
||||||
}
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
|
||||||
public void testGetException() {
|
|
||||||
assertEquals(client.downloadException("", tail(1)).trim(), "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
|
||||||
public void testGetSynchException() {
|
|
||||||
assertEquals(client.synchException("", "").trim(), "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
|
||||||
public void testGetStringRedirect() {
|
|
||||||
assertEquals(client.download("redirect").trim(), XML2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(invocationCount = 100, timeOut = 5000)
|
|
||||||
public void testGetBigFile() throws IOException {
|
|
||||||
InputStream input = getConsitution();
|
|
||||||
try {
|
try {
|
||||||
assertValidMd5(input);
|
String result = client.synch(uri);
|
||||||
} catch (RuntimeException e) {
|
RecordedRequest request = server.takeRequest();
|
||||||
closeQuietly(input);
|
assertTrue(URLDecoder.decode(request.getPath(), "UTF-8").endsWith(uri));
|
||||||
// since we are parsing client side, and not through a response
|
assertEquals(result, XML);
|
||||||
// handler, the user must retry directly. In this case, we are assuming
|
|
||||||
// lightning doesn't strike twice in the same spot.
|
|
||||||
if (getFirstThrowableOfType(e, IOException.class) != null) {
|
|
||||||
input = getConsitution();
|
|
||||||
assertValidMd5(input);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
closeQuietly(input);
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertValidMd5(final InputStream input) throws IOException {
|
@Test
|
||||||
|
public void testGetException() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(404));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.downloadException("", tail(1));
|
||||||
|
assertEquals(result, "foo");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSynchException() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(404));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.synchException("", "");
|
||||||
|
assertEquals(result, "foo");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetStringRedirect() throws Exception {
|
||||||
|
MockWebServer redirectTarget = mockWebServer(new MockResponse().setBody(XML2));
|
||||||
|
redirectTarget.useHttps(sslContext.getSocketFactory(), false);
|
||||||
|
MockWebServer server = mockWebServer((new MockResponse().setResponseCode(302).setHeader("Location",
|
||||||
|
redirectTarget.getUrl("/").toString())));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.download("redirect");
|
||||||
|
assertEquals(result, XML2);
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
assertEquals(redirectTarget.getRequestCount(), 1);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
redirectTarget.shutdown();
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetBigFile() throws Exception {
|
||||||
|
MockResponse response = new MockResponse().addHeader("Content-MD5", constitutionsMd5)
|
||||||
|
.addHeader("Content-type", "text/plain")
|
||||||
|
.setBody(oneHundredOneConstitutions.getInput(), constitutionsLength);
|
||||||
|
|
||||||
|
MockWebServer server = mockWebServer(response, response);
|
||||||
|
InputStream input = server.getUrl("/101constitutions").openStream();
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertValidMd5(input, constitutionsMd5);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
} finally {
|
||||||
|
close(input, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertValidMd5(final InputStream input, String md5) throws IOException {
|
||||||
assertEquals(base64().encode(asByteSource(input).hash(md5()).asBytes()), md5);
|
assertEquals(base64().encode(asByteSource(input).hash(md5()).asBytes()), md5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream getConsitution() throws MalformedURLException, IOException {
|
private static class MD5CheckDispatcher extends Dispatcher {
|
||||||
URI constitutionUri = URI.create(format("http://localhost:%d/101constitutions", testPort));
|
|
||||||
return constitutionUri.toURL().openStream();
|
@Override
|
||||||
|
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
|
||||||
|
try {
|
||||||
|
MockResponse response = new MockResponse();
|
||||||
|
String expectedMd5 = request.getHeader("Content-MD5");
|
||||||
|
ByteSource body = ByteSource.wrap(request.getBody());
|
||||||
|
String realMd5FromRequest = base64().encode(body.hash(md5()).asBytes());
|
||||||
|
boolean matched = expectedMd5.equals(realMd5FromRequest);
|
||||||
|
if (matched) {
|
||||||
|
response.addHeader("x-Content-MD5", realMd5FromRequest);
|
||||||
|
} else {
|
||||||
|
response.setResponseCode(500);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw Throwables.propagate(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
}
|
||||||
* Tests sending a big file to the server. Note: this is a heavy test, takes
|
|
||||||
* several minutes to finish.
|
@Test
|
||||||
*/
|
public void testUploadBigFile() throws Exception {
|
||||||
@Test(invocationCount = 1)
|
MockWebServer server = mockWebServer(new MD5CheckDispatcher());
|
||||||
public void testUploadBigFile() throws IOException {
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
String filename = "jclouds";
|
|
||||||
File f = null;
|
File f = null;
|
||||||
|
Payload payload = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// create a file, twice big as free heap memory
|
f = File.createTempFile("jclouds", "tmp");
|
||||||
f = File.createTempFile(filename, "tmp");
|
|
||||||
f.deleteOnExit();
|
f.deleteOnExit();
|
||||||
long length = (long) (Runtime.getRuntime().freeMemory() * 1.1);
|
long length = (new Random().nextInt(32) + 1) * 1024 * 1024;
|
||||||
|
|
||||||
MessageDigest digester = md5Digest();
|
MessageDigest digester = md5Digest();
|
||||||
|
|
||||||
|
@ -165,19 +296,29 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
|
||||||
}
|
}
|
||||||
out.flush();
|
out.flush();
|
||||||
} finally {
|
} finally {
|
||||||
closeQuietly(out);
|
close(out, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Payload payload = newFilePayload(f);
|
payload = newByteSourcePayload(asByteSource(f));
|
||||||
byte[] digest = digester.digest();
|
byte[] digest = digester.digest();
|
||||||
|
String strDigest = base64().encode(digest);
|
||||||
|
|
||||||
payload.getContentMetadata().setContentMD5(digest);
|
payload.getContentMetadata().setContentMD5(digest);
|
||||||
|
payload.getContentMetadata().setContentLength(f.length());
|
||||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||||
assertEquals(headers.get("x-Content-MD5"), ImmutableList.of(base64().encode(digest)));
|
RecordedRequest request = server.takeRequest();
|
||||||
payload.release();
|
assertEquals(request.getHeader("Content-MD5"), strDigest);
|
||||||
|
assertEquals(headers.get("x-Content-MD5"), ImmutableList.of(strDigest));
|
||||||
} finally {
|
} finally {
|
||||||
if (f != null && f.exists())
|
if (payload != null) {
|
||||||
|
payload.release();
|
||||||
|
}
|
||||||
|
if (f != null && f.exists()) {
|
||||||
f.delete();
|
f.delete();
|
||||||
}
|
}
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageDigest md5Digest() throws AssertionError {
|
private MessageDigest md5Digest() throws AssertionError {
|
||||||
|
@ -188,77 +329,334 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testPost() {
|
public void testPost() throws Exception {
|
||||||
assertEquals(client.post("", "foo").trim(), "fooPOST");
|
MockWebServer server = mockWebServer(new MockResponse().setBody("fooPOST"));
|
||||||
}
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
|
||||||
@Test(invocationCount = 1, timeOut = 5000)
|
|
||||||
public void testPostAsInputStream() {
|
|
||||||
AtomicInteger postFailures = new AtomicInteger();
|
|
||||||
for (int i = 0; i < 5; i++)
|
|
||||||
try {
|
try {
|
||||||
assertEquals(client.postAsInputStream("", "foo").trim(), "fooPOST");
|
String result = client.post("", "foo");
|
||||||
} catch (Exception e) {
|
// Verify that the body is properly populated
|
||||||
postFailures.incrementAndGet();
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
assertEquals(result, "fooPOST");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
}
|
}
|
||||||
assertTrue(postFailures.get() > 0, "expected failures");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testPostBinder() {
|
public void testZeroLengthPost() throws Exception {
|
||||||
assertEquals(client.postJson("", "foo").trim(), "{\"key\":\"foo\"}POST");
|
MockWebServer server = mockWebServer(new MockResponse());
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
client.postNothing("");
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testPostContentDisposition() {
|
public void testPostIsRetriedOnFailure() throws Exception {
|
||||||
Payload payload = newStringPayload("foo");
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500),
|
||||||
|
new MockResponse().setBody("fooPOST"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.post("", "foo");
|
||||||
|
assertEquals(server.getRequestCount(), 2);
|
||||||
|
assertEquals(result, "fooPOST");
|
||||||
|
// Verify that the body was properly sent in the two requests
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostRedirect() throws Exception {
|
||||||
|
MockWebServer redirectTarget = mockWebServer(new MockResponse().setBody("fooPOSTREDIRECT"));
|
||||||
|
redirectTarget.useHttps(sslContext.getSocketFactory(), false);
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(302).setHeader("Location",
|
||||||
|
redirectTarget.getUrl("/").toString()));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.post("redirect", "foo");
|
||||||
|
assertEquals(result, "fooPOSTREDIRECT");
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
assertEquals(redirectTarget.getRequestCount(), 1);
|
||||||
|
// Verify that the body was populated after the redirect
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
request = redirectTarget.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
redirectTarget.shutdown();
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostAsInputStream() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setBody("fooPOST"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.postAsInputStream("", "foo");
|
||||||
|
// Verify that the body is properly populated
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
assertEquals(result, "fooPOST");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostAsInputStreamDoesNotRetryOnFailure() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500), new MockResponse());
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
client.postAsInputStream("", "foo");
|
||||||
|
fail("Request should have thrown an exception after a server error");
|
||||||
|
} catch (Exception expected) {
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostBinder() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setBody("fooPOSTJSON"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.postJson("", "foo");
|
||||||
|
// Verify that the body is properly populated
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "{\"key\":\"foo\"}");
|
||||||
|
assertEquals(result, "fooPOSTJSON");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostContentDisposition() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().addHeader("x-Content-Disposition",
|
||||||
|
"attachment; filename=photo.jpg"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
Payload payload = null;
|
||||||
|
try {
|
||||||
|
ByteSource body = ByteSource.wrap("foo".getBytes());
|
||||||
|
payload = newByteSourcePayload(body);
|
||||||
payload.getContentMetadata().setContentDisposition("attachment; filename=photo.jpg");
|
payload.getContentMetadata().setContentDisposition("attachment; filename=photo.jpg");
|
||||||
|
payload.getContentMetadata().setContentLength(body.size());
|
||||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getHeader("Content-Disposition"), "attachment; filename=photo.jpg");
|
||||||
assertEquals(headers.get("x-Content-Disposition"), ImmutableList.of("attachment; filename=photo.jpg"));
|
assertEquals(headers.get("x-Content-Disposition"), ImmutableList.of("attachment; filename=photo.jpg"));
|
||||||
|
} finally {
|
||||||
|
if (payload != null) {
|
||||||
payload.release();
|
payload.release();
|
||||||
}
|
}
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testPostContentEncoding() {
|
public void testPostContentEncoding() throws Exception {
|
||||||
Payload payload = newStringPayload("foo");
|
MockWebServer server = mockWebServer(new MockResponse().addHeader("x-Content-Encoding", "gzip"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
Payload payload = null;
|
||||||
|
try {
|
||||||
|
ByteSource body = ByteSource.wrap("foo".getBytes());
|
||||||
|
payload = newByteSourcePayload(body);
|
||||||
payload.getContentMetadata().setContentEncoding("gzip");
|
payload.getContentMetadata().setContentEncoding("gzip");
|
||||||
|
payload.getContentMetadata().setContentLength(body.size());
|
||||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getHeader("Content-Encoding"), "gzip");
|
||||||
assertEquals(headers.get("x-Content-Encoding"), ImmutableList.of("gzip"));
|
assertEquals(headers.get("x-Content-Encoding"), ImmutableList.of("gzip"));
|
||||||
|
} finally {
|
||||||
|
if (payload != null) {
|
||||||
payload.release();
|
payload.release();
|
||||||
}
|
}
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testPostContentLanguage() {
|
public void testPostContentLanguage() throws Exception {
|
||||||
Payload payload = newStringPayload("foo");
|
MockWebServer server = mockWebServer(new MockResponse().addHeader("x-Content-Language", "mi, en"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
Payload payload = null;
|
||||||
|
try {
|
||||||
|
ByteSource body = ByteSource.wrap("foo".getBytes());
|
||||||
|
payload = newByteSourcePayload(body);
|
||||||
payload.getContentMetadata().setContentLanguage("mi, en");
|
payload.getContentMetadata().setContentLanguage("mi, en");
|
||||||
|
payload.getContentMetadata().setContentLength(body.size());
|
||||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getHeader("Content-Language"), "mi, en");
|
||||||
assertEquals(headers.get("x-Content-Language"), ImmutableList.of("mi, en"));
|
assertEquals(headers.get("x-Content-Language"), ImmutableList.of("mi, en"));
|
||||||
|
} finally {
|
||||||
|
if (payload != null) {
|
||||||
payload.release();
|
payload.release();
|
||||||
}
|
}
|
||||||
|
close(client, true);
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
server.shutdown();
|
||||||
public void testPut() {
|
}
|
||||||
assertEquals(client.upload("", "foo").trim(), "fooPUT");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testPutRedirect() {
|
public void testPut() throws Exception {
|
||||||
assertEquals(client.upload("redirect", "foo").trim(), "fooPUTREDIRECT");
|
MockWebServer server = mockWebServer(new MockResponse().setBody("fooPUT"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.upload("", "foo");
|
||||||
|
// Verify that the body is properly populated
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
assertEquals(result, "fooPUT");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testHead() {
|
public void testPutRedirect() throws Exception {
|
||||||
assertTrue(client.exists(""), "head returned false");
|
MockWebServer redirectTarget = mockWebServer(new MockResponse().setBody("fooPUTREDIRECT"));
|
||||||
|
redirectTarget.useHttps(sslContext.getSocketFactory(), false);
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(302).setHeader("Location",
|
||||||
|
redirectTarget.getUrl("/").toString()));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.upload("redirect", "foo");
|
||||||
|
assertEquals(result, "fooPUTREDIRECT");
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
assertEquals(redirectTarget.getRequestCount(), 1);
|
||||||
|
// Verify that the body was populated after the redirect
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
request = redirectTarget.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
redirectTarget.shutdown();
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
@Test
|
||||||
public void testGetAndParseSax() {
|
public void testZeroLengthPut() throws Exception {
|
||||||
assertEquals(client.downloadAndParse(""), "whoppers");
|
MockWebServer server = mockWebServer(new MockResponse());
|
||||||
}
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
@Test(invocationCount = 5, timeOut = 5000)
|
|
||||||
public void testZeroLengthPut() {
|
|
||||||
client.putNothing("");
|
client.putNothing("");
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutIsRetriedOnFailure() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500),
|
||||||
|
new MockResponse().setBody("fooPUT"));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.upload("", "foo");
|
||||||
|
assertEquals(server.getRequestCount(), 2);
|
||||||
|
assertEquals(result, "fooPUT");
|
||||||
|
// Verify that the body was properly sent in the two requests
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHead() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse());
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
assertTrue(client.exists(""));
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeadIsRetriedOnServerError() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500), new MockResponse());
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
assertTrue(client.exists(""));
|
||||||
|
assertEquals(server.getRequestCount(), 2);
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeadFailure() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(404));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
assertFalse(client.exists(""));
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAndParseSax() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setBody(XML));
|
||||||
|
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = client.downloadAndParse("");
|
||||||
|
assertEquals(result, "whoppers");
|
||||||
|
} finally {
|
||||||
|
close(client, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private InputSupplier<InputStream> getTestDataSupplier() throws IOException {
|
||||||
|
byte[] oneConstitution = toByteArray(new GZIPInputStream(
|
||||||
|
BaseHttpCommandExecutorServiceIntegrationTest.class.getResourceAsStream("/const.txt.gz")));
|
||||||
|
InputSupplier<ByteArrayInputStream> constitutionSupplier = newInputStreamSupplier(oneConstitution);
|
||||||
|
InputSupplier<InputStream> temp = join(constitutionSupplier);
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
temp = join(temp, constitutionSupplier);
|
||||||
|
}
|
||||||
|
constitutionsLength = oneConstitution.length * 101;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jclouds.http;
|
||||||
|
|
||||||
|
import static org.jclouds.Constants.PROPERTY_RELAX_HOSTNAME;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_TRUST_ALL_CERTS;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import org.jclouds.ContextBuilder;
|
||||||
|
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
|
||||||
|
import org.jclouds.providers.AnonymousProviderMetadata;
|
||||||
|
import org.testng.annotations.BeforeClass;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.net.HttpHeaders;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.squareup.okhttp.internal.SslContextBuilder;
|
||||||
|
import com.squareup.okhttp.mockwebserver.Dispatcher;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||||
|
import com.squareup.okhttp.mockwebserver.QueueDispatcher;
|
||||||
|
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for integration tests that use {@link MockWebServer} to verify the
|
||||||
|
* behavior of the HTTP workflows.
|
||||||
|
*
|
||||||
|
* @author Ignasi Barrera
|
||||||
|
*/
|
||||||
|
public abstract class BaseMockWebServerTest {
|
||||||
|
|
||||||
|
protected SSLContext sslContext;
|
||||||
|
|
||||||
|
@BeforeClass(groups = "integration")
|
||||||
|
protected void setupSSL() {
|
||||||
|
try {
|
||||||
|
sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build();
|
||||||
|
} catch (GeneralSecurityException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
} catch (UnknownHostException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class GlobalChecksRequestDispatcher extends QueueDispatcher {
|
||||||
|
@Override
|
||||||
|
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
|
||||||
|
MockResponse response = responseQueue.take();
|
||||||
|
if (!HttpRequest.NON_PAYLOAD_METHODS.contains(request.getMethod())
|
||||||
|
&& request.getHeader(HttpHeaders.CONTENT_LENGTH) == null) {
|
||||||
|
response.setResponseCode(500);
|
||||||
|
response.setBody("No content length!");
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link MockWebServer} that uses the
|
||||||
|
* {@link GlobalChecksRequestDispatcher}.
|
||||||
|
*/
|
||||||
|
protected static MockWebServer mockWebServer(MockResponse... responses) throws IOException {
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
server.play();
|
||||||
|
server.setDispatcher(new GlobalChecksRequestDispatcher());
|
||||||
|
for (MockResponse response : responses) {
|
||||||
|
server.enqueue(response);
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link MockWebServer} that uses the given {@link Dispatcher}.
|
||||||
|
*/
|
||||||
|
protected static MockWebServer mockWebServer(Dispatcher dispatcher) throws IOException {
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
server.play();
|
||||||
|
server.setDispatcher(dispatcher);
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a test api for the given class and URL.
|
||||||
|
*/
|
||||||
|
protected <T extends Closeable> T api(Class<T> apiClass, String url) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty(PROPERTY_TRUST_ALL_CERTS, "true");
|
||||||
|
properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true");
|
||||||
|
addOverrideProperties(properties);
|
||||||
|
return ContextBuilder.newBuilder(AnonymousProviderMetadata.forApiOnEndpoint(apiClass, url))
|
||||||
|
.modules(ImmutableSet.<Module> of(createConnectionModule())).overrides(properties).buildApi(apiClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the connection properties used to configure the tests.
|
||||||
|
*/
|
||||||
|
protected abstract void addOverrideProperties(Properties props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the connection module that provides the HTTP driver to use in the
|
||||||
|
* tests.
|
||||||
|
* <p>
|
||||||
|
* Unless a concrete HTTP is required, subclasses may want to use the
|
||||||
|
* {@link JavaUrlHttpCommandExecutorServiceModule}.
|
||||||
|
*/
|
||||||
|
protected abstract Module createConnectionModule();
|
||||||
|
|
||||||
|
}
|
|
@ -196,6 +196,10 @@ public interface IntegrationTestAsyncClient extends Closeable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/objects/{id}")
|
||||||
|
ListenableFuture<Void> postNothing(@PathParam("id") String id);
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Path("/objects/{id}")
|
@Path("/objects/{id}")
|
||||||
ListenableFuture<Void> putNothing(@PathParam("id") String id);
|
ListenableFuture<Void> putNothing(@PathParam("id") String id);
|
||||||
|
|
|
@ -199,6 +199,10 @@ public interface IntegrationTestClient extends Closeable {
|
||||||
@Path("/objects/{id}")
|
@Path("/objects/{id}")
|
||||||
void putNothing(@PathParam("id") String id);
|
void putNothing(@PathParam("id") String id);
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/objects/{id}")
|
||||||
|
void postNothing(@PathParam("id") String id);
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
StringBuilder newStringBuilder();
|
StringBuilder newStringBuilder();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,14 +33,14 @@ import com.google.inject.Module;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test(groups = "integration")
|
||||||
public class JavaUrlHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
public class JavaUrlHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
||||||
|
|
||||||
protected Module createConnectionModule() {
|
protected Module createConnectionModule() {
|
||||||
return new JavaUrlHttpCommandExecutorServiceModule();
|
return new JavaUrlHttpCommandExecutorServiceModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addConnectionProperties(Properties props) {
|
protected void addOverrideProperties(Properties props) {
|
||||||
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_CONTEXT, 50 + "");
|
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_CONTEXT, 50 + "");
|
||||||
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_HOST, 0 + "");
|
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_HOST, 0 + "");
|
||||||
// IO workers not used in this executor
|
// IO workers not used in this executor
|
||||||
|
|
|
@ -21,14 +21,15 @@ import static org.testng.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.jclouds.http.BaseJettyTest;
|
import org.jclouds.ContextBuilder;
|
||||||
import org.jclouds.http.HttpCommand;
|
import org.jclouds.http.HttpCommand;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.IntegrationTestAsyncClient;
|
import org.jclouds.http.IntegrationTestAsyncClient;
|
||||||
|
import org.jclouds.http.IntegrationTestClient;
|
||||||
import org.jclouds.io.Payloads;
|
import org.jclouds.io.Payloads;
|
||||||
|
import org.jclouds.providers.AnonymousProviderMetadata;
|
||||||
import org.jclouds.reflect.Invocation;
|
import org.jclouds.reflect.Invocation;
|
||||||
import org.jclouds.rest.internal.RestAnnotationProcessor;
|
import org.jclouds.rest.internal.RestAnnotationProcessor;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
@ -122,8 +123,10 @@ public class BackoffLimitedRetryHandlerTest {
|
||||||
assertEquals(response.getPayload().getInput().read(), -1);
|
assertEquals(response.getPayload().getInput().read(), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Function<Invocation, HttpRequest> processor = BaseJettyTest.newBuilder(8100, new Properties()).buildInjector()
|
private final Function<Invocation, HttpRequest> processor = ContextBuilder
|
||||||
.getInstance(RestAnnotationProcessor.class);
|
.newBuilder(AnonymousProviderMetadata.forApiOnEndpoint(IntegrationTestClient.class, "http://localhost"))
|
||||||
|
.buildInjector().getInstance(RestAnnotationProcessor.class);
|
||||||
|
|
||||||
|
|
||||||
private HttpCommand createCommand() throws SecurityException, NoSuchMethodException {
|
private HttpCommand createCommand() throws SecurityException, NoSuchMethodException {
|
||||||
Invokable<IntegrationTestAsyncClient, String> method = method(IntegrationTestAsyncClient.class, "download", String.class);
|
Invokable<IntegrationTestAsyncClient, String> method = method(IntegrationTestAsyncClient.class, "download", String.class);
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class ApacheHCHttpCommandExecutorServiceTestDisabled extends BaseHttpComm
|
||||||
return new ApacheHCHttpCommandExecutorServiceModule();
|
return new ApacheHCHttpCommandExecutorServiceModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addConnectionProperties(Properties props) {
|
protected void addOverrideProperties(Properties props) {
|
||||||
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_CONTEXT, 20 + "");
|
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_CONTEXT, 20 + "");
|
||||||
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_HOST, 0 + "");
|
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_HOST, 0 + "");
|
||||||
props.setProperty(PROPERTY_CONNECTION_TIMEOUT, 100 + "");
|
props.setProperty(PROPERTY_CONNECTION_TIMEOUT, 100 + "");
|
||||||
|
|
|
@ -61,7 +61,6 @@
|
||||||
<artifactId>appengine-api-1.0-sdk</artifactId>
|
<artifactId>appengine-api-1.0-sdk</artifactId>
|
||||||
<version>1.6.5</version>
|
<version>1.6.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.jclouds</groupId>
|
<groupId>org.apache.jclouds</groupId>
|
||||||
<artifactId>jclouds-core</artifactId>
|
<artifactId>jclouds-core</artifactId>
|
||||||
|
@ -70,8 +69,8 @@
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>mockwebserver</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -19,16 +19,12 @@ package org.jclouds.gae;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.jclouds.concurrent.SingleThreaded;
|
import org.jclouds.concurrent.SingleThreaded;
|
||||||
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
||||||
import org.jclouds.gae.config.GoogleAppEngineConfigurationModule;
|
import org.jclouds.gae.config.GoogleAppEngineConfigurationModule;
|
||||||
import org.jclouds.http.BaseHttpCommandExecutorServiceIntegrationTest;
|
import org.jclouds.http.BaseHttpCommandExecutorServiceIntegrationTest;
|
||||||
import org.jclouds.http.HttpCommandExecutorService;
|
import org.jclouds.http.HttpCommandExecutorService;
|
||||||
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
|
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
|
||||||
import org.jclouds.logging.Logger;
|
|
||||||
import org.testng.SkipException;
|
import org.testng.SkipException;
|
||||||
import org.testng.annotations.BeforeMethod;
|
import org.testng.annotations.BeforeMethod;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
@ -46,16 +42,11 @@ import com.google.inject.Module;
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
||||||
Logger logger = Logger.CONSOLE;
|
|
||||||
|
|
||||||
@Override
|
@BeforeMethod
|
||||||
protected void setupAndStartSSLServer(final int testPort) throws Exception {
|
public void setupApiProxy() {
|
||||||
}
|
LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig());
|
||||||
|
helper.setUp();
|
||||||
@Override
|
|
||||||
protected boolean redirectEveryTwentyRequests(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws IOException {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,103 +55,18 @@ public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
public void testPostAsInputStreamDoesNotRetryOnFailure() throws Exception {
|
||||||
public void testPostBinder() {
|
throw new SkipException("streams aren't supported");
|
||||||
setupApiProxy();
|
|
||||||
super.testPostBinder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeMethod
|
|
||||||
void setupApiProxy() {
|
|
||||||
LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig());
|
|
||||||
helper.setUp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
public void testGetBigFile() {
|
||||||
public void testGetAndParseSax() {
|
throw new SkipException("test data is too big for GAE");
|
||||||
setupApiProxy();
|
|
||||||
super.testGetAndParseSax();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
public void testUploadBigFile() throws IOException {
|
||||||
public void testGetString() {
|
throw new SkipException("test data is too big for GAE");
|
||||||
setupApiProxy();
|
|
||||||
super.testGetString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000, dataProvider = "gets")
|
|
||||||
public void testGetStringSynch(String path) {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testGetStringSynch(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testGetStringRedirect() {
|
|
||||||
throw new SkipException("need to get redirects to operate");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testGetException() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testGetException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testGetSynchException() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testGetSynchException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testPost() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testPost();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testPut() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testPut();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testGetStringViaRequest() throws IOException {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testGetStringViaRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testPutRedirect() {
|
|
||||||
throw new SkipException("need to get redirects to operate");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testGetStringWithHeader() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testGetStringWithHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testHead() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testHead();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testRequestFilter() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testRequestFilter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Module createConnectionModule() {
|
protected Module createConnectionModule() {
|
||||||
|
@ -184,45 +90,8 @@ public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addConnectionProperties(Properties props) {
|
protected void addOverrideProperties(Properties props) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testGetBigFile() {
|
|
||||||
throw new SkipException("test data is too big for GAE");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testUploadBigFile() throws IOException {
|
|
||||||
throw new SkipException("test data is too big for GAE");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testPostContentDisposition() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testPostContentDisposition();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testPostContentEncoding() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testPostContentEncoding();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
|
||||||
public void testPostContentLanguage() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testPostContentLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://code.google.com/p/googleappengine/issues/detail?id=3599
|
|
||||||
@Override
|
|
||||||
@Test(enabled = true, expectedExceptions = IllegalArgumentException.class)
|
|
||||||
public void testAlternateMethod() {
|
|
||||||
setupApiProxy();
|
|
||||||
super.testAlternateMethod();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
jclouds OkHttp driver
|
||||||
|
=====================
|
||||||
|
|
||||||
|
A driver to use the OkHttp (http://square.github.io/okhttp/) client as an HTTP library in jclouds.
|
||||||
|
|
||||||
|
This driver adds support for use of modern HTTP verbs such as PATCH in providers and APIs, and also supports SPDY.
|
||||||
|
|
||||||
|
To use the driver, you just need to include the `OkHttpCommandExecutorServiceModule` when creating
|
||||||
|
the context:
|
||||||
|
|
||||||
|
ContextBuilder.newBuilder("provider")
|
||||||
|
.endpoint("endpoint")
|
||||||
|
.credentials("identity", "credential")
|
||||||
|
.modules(ImmutableSet.of(new OkHttpCommandExecutorServiceModule()))
|
||||||
|
.build();
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.jclouds</groupId>
|
||||||
|
<artifactId>jclouds-project</artifactId>
|
||||||
|
<version>1.8.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../project/pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<groupId>org.apache.jclouds.driver</groupId>
|
||||||
|
<artifactId>jclouds-okhttp</artifactId>
|
||||||
|
<name>jclouds OkHttp Driver</name>
|
||||||
|
<packaging>bundle</packaging>
|
||||||
|
<description>OkHttp Driver</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<jclouds.osgi.export>org.jclouds.http.okhttp*;version="${project.version}"</jclouds.osgi.export>
|
||||||
|
<jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.jclouds</groupId>
|
||||||
|
<artifactId>jclouds-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.jclouds</groupId>
|
||||||
|
<artifactId>jclouds-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>${okhttp.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jclouds.http.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
|
import org.jclouds.Constants;
|
||||||
|
import org.jclouds.http.HttpRequest;
|
||||||
|
import org.jclouds.http.HttpUtils;
|
||||||
|
import org.jclouds.http.IOExceptionRetryHandler;
|
||||||
|
import org.jclouds.http.handlers.DelegatingErrorHandler;
|
||||||
|
import org.jclouds.http.handlers.DelegatingRetryHandler;
|
||||||
|
import org.jclouds.http.internal.HttpWire;
|
||||||
|
import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
|
||||||
|
import org.jclouds.io.ContentMetadataCodec;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the <code>HttpCommandExecutorService</code> that uses the
|
||||||
|
* OkHttp client to support modern HTTP methods such as PATCH.
|
||||||
|
*
|
||||||
|
* @author Ignasi Barrera
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class OkHttpCommandExecutorService extends JavaUrlHttpCommandExecutorService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public OkHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
|
||||||
|
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ListeningExecutorService ioExecutor,
|
||||||
|
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
|
||||||
|
DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
|
||||||
|
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider, Function<URI, Proxy> proxyForURI)
|
||||||
|
throws SecurityException, NoSuchFieldException {
|
||||||
|
super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier,
|
||||||
|
untrustedSSLContextProvider, proxyForURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpURLConnection initConnection(HttpRequest request) throws IOException {
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
URL url = request.getEndpoint().toURL();
|
||||||
|
client.setProxy(proxyForURI.apply(request.getEndpoint()));
|
||||||
|
if (url.getProtocol().equalsIgnoreCase("https")) {
|
||||||
|
if (utils.relaxHostname()) {
|
||||||
|
client.setHostnameVerifier(verifier);
|
||||||
|
}
|
||||||
|
if (sslContextSupplier != null) {
|
||||||
|
// used for providers which e.g. use certs for authentication (like
|
||||||
|
// FGCP) Provider provides SSLContext impl (which inits context with
|
||||||
|
// key manager)
|
||||||
|
client.setSslSocketFactory(sslContextSupplier.get().getSocketFactory());
|
||||||
|
} else if (utils.trustAllCerts()) {
|
||||||
|
client.setSslSocketFactory(untrustedSSLContextProvider.get().getSocketFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureRequestHeaders(HttpURLConnection connection, HttpRequest request) {
|
||||||
|
super.configureRequestHeaders(connection, request);
|
||||||
|
// OkHttp does not set the Accept header if not present in the request.
|
||||||
|
// Make sure we send a flexible one.
|
||||||
|
if (request.getFirstHeaderOrNull(HttpHeaders.ACCEPT) == null) {
|
||||||
|
connection.setRequestProperty(HttpHeaders.ACCEPT, "*/*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jclouds.http.okhttp.config;
|
||||||
|
|
||||||
|
import org.jclouds.http.HttpCommandExecutorService;
|
||||||
|
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
|
||||||
|
import org.jclouds.http.config.SSLModule;
|
||||||
|
import org.jclouds.http.okhttp.OkHttpCommandExecutorService;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Scopes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the {@link OkHttpCommandExecutorService}.
|
||||||
|
*
|
||||||
|
* Note that this uses threads.
|
||||||
|
*
|
||||||
|
* @author Ignasi Barrera
|
||||||
|
*/
|
||||||
|
@ConfiguresHttpCommandExecutorService
|
||||||
|
public class OkHttpCommandExecutorServiceModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
install(new SSLModule());
|
||||||
|
bind(HttpCommandExecutorService.class).to(OkHttpCommandExecutorService.class).in(Scopes.SINGLETON);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jclouds.http.okhttp;
|
||||||
|
|
||||||
|
import static com.google.common.io.Closeables.close;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_MAX_CONNECTIONS_PER_CONTEXT;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_MAX_CONNECTIONS_PER_HOST;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_USER_THREADS;
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
|
||||||
|
import org.jclouds.http.BaseHttpCommandExecutorServiceIntegrationTest;
|
||||||
|
import org.jclouds.http.okhttp.config.OkHttpCommandExecutorServiceModule;
|
||||||
|
import org.jclouds.rest.annotations.BinderParam;
|
||||||
|
import org.jclouds.rest.annotations.PATCH;
|
||||||
|
import org.jclouds.rest.binders.BindToStringPayload;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||||
|
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the functionality of the {@link OkHttpCommandExecutorService}
|
||||||
|
*
|
||||||
|
* @author Ignasi Barrera
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public class OkHttpCommandExecutorServiceTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Module createConnectionModule() {
|
||||||
|
return new OkHttpCommandExecutorServiceModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addOverrideProperties(final Properties props) {
|
||||||
|
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_CONTEXT, 50 + "");
|
||||||
|
props.setProperty(PROPERTY_MAX_CONNECTIONS_PER_HOST, 0 + "");
|
||||||
|
// IO workers not used in this executor
|
||||||
|
props.setProperty(PROPERTY_IO_WORKER_THREADS, 0 + "");
|
||||||
|
props.setProperty(PROPERTY_USER_THREADS, 5 + "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface PatchApi extends Closeable {
|
||||||
|
@PATCH
|
||||||
|
@Path("/objects/{id}")
|
||||||
|
String patch(@PathParam("id") String id, @BinderParam(BindToStringPayload.class) String body);
|
||||||
|
|
||||||
|
@PATCH
|
||||||
|
@Path("/objects/{id}")
|
||||||
|
String patchNothing(@PathParam("id") String id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatch() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setBody("fooPATCH"));
|
||||||
|
PatchApi api = api(PatchApi.class, server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = api.patch("", "foo");
|
||||||
|
// Verify that the body is properly populated
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "PATCH");
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
assertEquals(result, "fooPATCH");
|
||||||
|
} finally {
|
||||||
|
close(api, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchIsRetriedOnFailure() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500),
|
||||||
|
new MockResponse().setBody("fooPATCH"));
|
||||||
|
PatchApi api = api(PatchApi.class, server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = api.patch("", "foo");
|
||||||
|
assertEquals(server.getRequestCount(), 2);
|
||||||
|
assertEquals(result, "fooPATCH");
|
||||||
|
// Verify that the body was properly sent in the two requests
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "PATCH");
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "PATCH");
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
} finally {
|
||||||
|
close(api, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchRedirect() throws Exception {
|
||||||
|
MockWebServer redirectTarget = mockWebServer(new MockResponse().setBody("fooPATCHREDIRECT"));
|
||||||
|
redirectTarget.useHttps(sslContext.getSocketFactory(), false);
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(302).setHeader("Location",
|
||||||
|
redirectTarget.getUrl("/").toString()));
|
||||||
|
PatchApi api = api(PatchApi.class, server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
String result = api.patch("", "foo");
|
||||||
|
assertEquals(result, "fooPATCHREDIRECT");
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
assertEquals(redirectTarget.getRequestCount(), 1);
|
||||||
|
// Verify that the body was populated after the redirect
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "PATCH");
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
request = redirectTarget.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "PATCH");
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "foo");
|
||||||
|
} finally {
|
||||||
|
close(api, true);
|
||||||
|
redirectTarget.shutdown();
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testZeroLengthPatch() throws Exception {
|
||||||
|
MockWebServer server = mockWebServer(new MockResponse());
|
||||||
|
PatchApi api = api(PatchApi.class, server.getUrl("/").toString());
|
||||||
|
try {
|
||||||
|
api.patchNothing("");
|
||||||
|
assertEquals(server.getRequestCount(), 1);
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "PATCH");
|
||||||
|
assertEquals(new String(request.getBody(), "UTF-8"), "");
|
||||||
|
} finally {
|
||||||
|
close(api, true);
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -39,5 +39,6 @@
|
||||||
<module>jsch</module>
|
<module>jsch</module>
|
||||||
<module>netty</module>
|
<module>netty</module>
|
||||||
<module>enterprise</module>
|
<module>enterprise</module>
|
||||||
|
<module>okhttp</module>
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -212,6 +212,7 @@
|
||||||
<jclouds.test.listener>org.jclouds.test.testng.UnitTestStatusListener</jclouds.test.listener>
|
<jclouds.test.listener>org.jclouds.test.testng.UnitTestStatusListener</jclouds.test.listener>
|
||||||
<test.ssh.keyfile />
|
<test.ssh.keyfile />
|
||||||
<sourceReleaseAssemblyDescriptor>source-release-zip-tar</sourceReleaseAssemblyDescriptor>
|
<sourceReleaseAssemblyDescriptor>source-release-zip-tar</sourceReleaseAssemblyDescriptor>
|
||||||
|
<okhttp.version>1.3.0</okhttp.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -251,6 +252,11 @@
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>8.1.8.v20121106</version>
|
<version>8.1.8.v20121106</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<version>${okhttp.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.testng</groupId>
|
<groupId>org.testng</groupId>
|
||||||
<artifactId>testng</artifactId>
|
<artifactId>testng</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue