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>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -82,6 +82,11 @@
|
|||
<artifactId>jetty-security</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<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.Throwables.propagate;
|
||||
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.HOST;
|
||||
import static com.google.common.net.HttpHeaders.USER_AGENT;
|
||||
import static org.jclouds.http.HttpUtils.filterOutContentHeaders;
|
||||
import static org.jclouds.io.Payloads.newInputStreamPayload;
|
||||
import static org.jclouds.util.Closeables2.closeQuietly;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -46,6 +46,7 @@ import javax.net.ssl.SSLContext;
|
|||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.JcloudsVersion;
|
||||
import org.jclouds.http.HttpCommandExecutorService;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
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
|
||||
.getProperty("java.version"));
|
||||
|
||||
private final Supplier<SSLContext> untrustedSSLContextProvider;
|
||||
private final Function<URI, Proxy> proxyForURI;
|
||||
private final HostnameVerifier verifier;
|
||||
protected final Supplier<SSLContext> untrustedSSLContextProvider;
|
||||
protected final Function<URI, Proxy> proxyForURI;
|
||||
protected final HostnameVerifier verifier;
|
||||
@Inject(optional = true)
|
||||
Supplier<SSLContext> sslContextSupplier;
|
||||
protected Supplier<SSLContext> sslContextSupplier;
|
||||
|
||||
@Inject
|
||||
public JavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
|
||||
|
@ -105,13 +106,13 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
|||
} catch (IOException e) {
|
||||
in = bufferAndCloseStream(connection.getErrorStream());
|
||||
} catch (RuntimeException e) {
|
||||
closeQuietly(in);
|
||||
close(in, true);
|
||||
throw propagate(e);
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 204) {
|
||||
closeQuietly(in);
|
||||
close(in, true);
|
||||
in = null;
|
||||
}
|
||||
builder.statusCode(responseCode);
|
||||
|
@ -141,7 +142,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
|||
in = new ByteArrayInputStream(toByteArray(inputStream));
|
||||
}
|
||||
} finally {
|
||||
closeQuietly(inputStream);
|
||||
close(inputStream, true);
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
@ -149,21 +150,8 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
|||
@Override
|
||||
protected HttpURLConnection convert(HttpRequest request) throws IOException, InterruptedException {
|
||||
boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding"));
|
||||
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());
|
||||
}
|
||||
}
|
||||
HttpURLConnection connection = initConnection(request);
|
||||
connection.setConnectTimeout(utils.getConnectionTimeout());
|
||||
connection.setReadTimeout(utils.getSocketOpenTimeout());
|
||||
connection.setAllowUserInteraction(false);
|
||||
|
@ -173,10 +161,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
|||
connection.setInstanceFollowRedirects(false);
|
||||
|
||||
setRequestMethodBypassingJREMethodLimitation(connection, request.getMethod());
|
||||
|
||||
for (Map.Entry<String, String> entry : request.getHeaders().entries()) {
|
||||
connection.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
configureRequestHeaders(connection, request);
|
||||
|
||||
String host = request.getEndpoint().getHost();
|
||||
if (request.getEndpoint().getPort() != -1) {
|
||||
|
@ -216,6 +201,36 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
|
|||
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>
|
||||
* The implementation of Sun Microsystems is throwing a <code>ProtocolException</code>
|
||||
|
|
|
@ -16,38 +16,37 @@
|
|||
*/
|
||||
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.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
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.handlers.BackoffLimitedRetryHandler;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
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
|
||||
* {@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.
|
||||
* {@link BackoffLimitedRetryHandler} to ensure that retries up to the default
|
||||
* limit succeed.
|
||||
*
|
||||
* @author James Murty
|
||||
* @author Ignasi Barrera
|
||||
*/
|
||||
@Test(sequential = true)
|
||||
public class BackoffLimitedRetryJavaTest extends BaseJettyTest {
|
||||
private int beginToFailOnRequestNumber = 0;
|
||||
private int endFailuresOnRequestNumber = 0;
|
||||
private int requestCount = 0;
|
||||
@Test(groups = "integration")
|
||||
public class BackoffLimitedRetryJavaTest extends BaseMockWebServerTest {
|
||||
|
||||
private final int maxRetries = 5;
|
||||
|
||||
@Override
|
||||
protected void addConnectionProperties(Properties props) {
|
||||
protected void addOverrideProperties(Properties props) {
|
||||
props.setProperty(PROPERTY_MAX_RETRIES, "" + maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,84 +54,98 @@ public class BackoffLimitedRetryJavaTest extends BaseJettyTest {
|
|||
return new JavaUrlHttpCommandExecutorServiceModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean failEveryTenRequests(HttpServletRequest request, HttpServletResponse response)
|
||||
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("");
|
||||
protected IntegrationTestClient client(String url) {
|
||||
return api(IntegrationTestClient.class, url);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoRetriesSuccessful() throws InterruptedException, ExecutionException {
|
||||
beginToFailOnRequestNumber = 1;
|
||||
endFailuresOnRequestNumber = 1;
|
||||
requestCount = 0;
|
||||
|
||||
assertEquals(submitGetRequest().trim(), XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleRetrySuccessful() throws InterruptedException, ExecutionException {
|
||||
beginToFailOnRequestNumber = 0;
|
||||
endFailuresOnRequestNumber = 1;
|
||||
requestCount = 0;
|
||||
|
||||
assertEquals(submitGetRequest().trim(), XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaximumRetriesSuccessful() throws InterruptedException, ExecutionException {
|
||||
beginToFailOnRequestNumber = 0;
|
||||
endFailuresOnRequestNumber = 5;
|
||||
requestCount = 0;
|
||||
|
||||
assertEquals(submitGetRequest().trim(), XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaximumRetriesExceeded() throws InterruptedException, ExecutionException {
|
||||
beginToFailOnRequestNumber = 0;
|
||||
endFailuresOnRequestNumber = 6;
|
||||
requestCount = 0;
|
||||
|
||||
public void testNoRetriesSuccessful() throws Exception {
|
||||
MockWebServer server = mockWebServer(new MockResponse());
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
try {
|
||||
submitGetRequest();
|
||||
fail("Request should not succeed within " + endFailuresOnRequestNumber + " requests");
|
||||
} catch (HttpResponseException e) {
|
||||
assertEquals(e.getResponse().getStatusCode(), 500);
|
||||
client.download("");
|
||||
assertEquals(server.getRequestCount(), 1);
|
||||
} 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);
|
||||
|
||||
public void testSingleRetrySuccessful() throws Exception {
|
||||
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(500), new MockResponse());
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
try {
|
||||
submitGetRequest();
|
||||
fail("Third request should not succeed by attempt number " + requestCount);
|
||||
} catch (HttpResponseException e) {
|
||||
assertEquals(e.getResponse().getStatusCode(), 500);
|
||||
client.download("");
|
||||
assertEquals(server.getRequestCount(), 2);
|
||||
} finally {
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaximumRetriesSuccessful() throws Exception {
|
||||
MockWebServer server = mockWebServer();
|
||||
for (int i = 0; i < maxRetries - 1; i++) {
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
}
|
||||
server.enqueue(new MockResponse());
|
||||
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
try {
|
||||
client.download("");
|
||||
assertEquals(server.getRequestCount(), maxRetries);
|
||||
} finally {
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaximumRetriesExceeded() throws Exception {
|
||||
MockWebServer server = mockWebServer();
|
||||
for (int i = 0; i <= maxRetries; i++) {
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
}
|
||||
|
||||
assertEquals(submitGetRequest().trim(), XML);
|
||||
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 {
|
||||
client.download("");
|
||||
fail("Request should not succeed within " + maxRetries + " requests");
|
||||
} catch (HttpResponseException ex) {
|
||||
assertEquals(ex.getResponse().getStatusCode(), 500);
|
||||
assertEquals(server.getRequestCount(), maxRetries + 3);
|
||||
}
|
||||
} finally {
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,72 +18,148 @@ package org.jclouds.http;
|
|||
|
||||
import static com.google.common.hash.Hashing.md5;
|
||||
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.io.ByteSources.asByteSource;
|
||||
import static org.jclouds.io.Payloads.newFilePayload;
|
||||
import static org.jclouds.io.Payloads.newStringPayload;
|
||||
import static org.jclouds.util.Closeables2.closeQuietly;
|
||||
import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
|
||||
import static org.jclouds.io.Payloads.newByteSourcePayload;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Writer;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.security.MessageDigest;
|
||||
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.util.Strings2;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.CharSink;
|
||||
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
|
||||
* services} must express. These tests will operate against an in-memory http
|
||||
* Tests for functionality all {@link HttpCommandExecutorService} http executor
|
||||
* services must express. These tests will operate against an in-memory http
|
||||
* engine, so as to ensure end-to-end functionality works.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
* @author Ignasi Barrera
|
||||
*/
|
||||
@Test(threadPoolSize = 10, groups = "integration")
|
||||
public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends BaseJettyTest {
|
||||
@Test(groups = "integration")
|
||||
public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends BaseMockWebServerTest {
|
||||
|
||||
@Test(invocationCount = 25, timeOut = 5000)
|
||||
public void testRequestFilter() {
|
||||
assertEquals(client.downloadFilter("", "filterme").trim(), "test");
|
||||
private static final String XML = "<foo><bar>whoppers</bar></foo>";
|
||||
private static final String XML2 = "<foo><bar>chubbs</bar></foo>";
|
||||
|
||||
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)
|
||||
public void testGetStringWithHeader() {
|
||||
assertEquals(client.download("", "test").trim(), "test");
|
||||
protected IntegrationTestClient client(String url) {
|
||||
return api(IntegrationTestClient.class, url);
|
||||
}
|
||||
|
||||
@Test(invocationCount = 1, timeOut = 5000)
|
||||
public void testAlternateMethod() {
|
||||
assertEquals(client.rowdy("").trim(), XML);
|
||||
@Test
|
||||
public void testRequestFilter() throws Exception {
|
||||
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)
|
||||
public void testGetString() {
|
||||
assertEquals(client.download("").trim(), XML);
|
||||
@Test
|
||||
public void testGetStringWithHeader() throws Exception {
|
||||
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)
|
||||
public void testGetStringViaRequest() throws IOException {
|
||||
HttpResponse getStringResponse = client.invoke(HttpRequest.builder().method("GET")
|
||||
.endpoint(format("http://localhost:%d/objects/", testPort)).build());
|
||||
assertEquals(Strings2.toString(getStringResponse.getPayload()).trim(), XML);
|
||||
@Test
|
||||
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")
|
||||
.endpoint(server.getUrl("/objects").toString()).build());
|
||||
assertEquals(Strings2.toString(getStringResponse.getPayload()).trim(), XML);
|
||||
} finally {
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@DataProvider(name = "gets")
|
||||
|
@ -91,68 +167,123 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
|
|||
return new Object[][] { { "object" }, { "/path" }, { "sp ace" }, { "unic₪de" }, { "qu?stion" } };
|
||||
}
|
||||
|
||||
@Test(invocationCount = 5, timeOut = 5000, dataProvider = "gets")
|
||||
public void testGetStringSynch(String uri) {
|
||||
assertEquals(client.synch(uri).trim(), XML);
|
||||
}
|
||||
|
||||
@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();
|
||||
@Test(dataProvider = "gets")
|
||||
public void testGetStringSynch(String uri) throws Exception {
|
||||
MockWebServer server = mockWebServer(new MockResponse().setBody(XML));
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
try {
|
||||
assertValidMd5(input);
|
||||
} catch (RuntimeException e) {
|
||||
closeQuietly(input);
|
||||
// since we are parsing client side, and not through a response
|
||||
// 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);
|
||||
}
|
||||
String result = client.synch(uri);
|
||||
RecordedRequest request = server.takeRequest();
|
||||
assertTrue(URLDecoder.decode(request.getPath(), "UTF-8").endsWith(uri));
|
||||
assertEquals(result, XML);
|
||||
} 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);
|
||||
}
|
||||
|
||||
private InputStream getConsitution() throws MalformedURLException, IOException {
|
||||
URI constitutionUri = URI.create(format("http://localhost:%d/101constitutions", testPort));
|
||||
return constitutionUri.toURL().openStream();
|
||||
private static class MD5CheckDispatcher extends Dispatcher {
|
||||
|
||||
@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(invocationCount = 1)
|
||||
public void testUploadBigFile() throws IOException {
|
||||
String filename = "jclouds";
|
||||
@Test
|
||||
public void testUploadBigFile() throws Exception {
|
||||
MockWebServer server = mockWebServer(new MD5CheckDispatcher());
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
|
||||
File f = null;
|
||||
Payload payload = null;
|
||||
|
||||
try {
|
||||
// create a file, twice big as free heap memory
|
||||
f = File.createTempFile(filename, "tmp");
|
||||
f = File.createTempFile("jclouds", "tmp");
|
||||
f.deleteOnExit();
|
||||
long length = (long) (Runtime.getRuntime().freeMemory() * 1.1);
|
||||
|
||||
long length = (new Random().nextInt(32) + 1) * 1024 * 1024;
|
||||
|
||||
MessageDigest digester = md5Digest();
|
||||
|
||||
CharSink fileSink = Files.asCharSink(f, Charsets.UTF_8);
|
||||
|
@ -165,18 +296,28 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
|
|||
}
|
||||
out.flush();
|
||||
} finally {
|
||||
closeQuietly(out);
|
||||
close(out, true);
|
||||
}
|
||||
|
||||
Payload payload = newFilePayload(f);
|
||||
payload = newByteSourcePayload(asByteSource(f));
|
||||
byte[] digest = digester.digest();
|
||||
String strDigest = base64().encode(digest);
|
||||
|
||||
payload.getContentMetadata().setContentMD5(digest);
|
||||
payload.getContentMetadata().setContentLength(f.length());
|
||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||
assertEquals(headers.get("x-Content-MD5"), ImmutableList.of(base64().encode(digest)));
|
||||
payload.release();
|
||||
RecordedRequest request = server.takeRequest();
|
||||
assertEquals(request.getHeader("Content-MD5"), strDigest);
|
||||
assertEquals(headers.get("x-Content-MD5"), ImmutableList.of(strDigest));
|
||||
} finally {
|
||||
if (f != null && f.exists())
|
||||
if (payload != null) {
|
||||
payload.release();
|
||||
}
|
||||
if (f != null && f.exists()) {
|
||||
f.delete();
|
||||
}
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,77 +329,334 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
|
|||
}
|
||||
}
|
||||
|
||||
@Test(invocationCount = 5, timeOut = 5000)
|
||||
public void testPost() {
|
||||
assertEquals(client.post("", "foo").trim(), "fooPOST");
|
||||
@Test
|
||||
public void testPost() throws Exception {
|
||||
MockWebServer server = mockWebServer(new MockResponse().setBody("fooPOST"));
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
try {
|
||||
String result = client.post("", "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(invocationCount = 1, timeOut = 5000)
|
||||
public void testPostAsInputStream() {
|
||||
AtomicInteger postFailures = new AtomicInteger();
|
||||
for (int i = 0; i < 5; i++)
|
||||
try {
|
||||
assertEquals(client.postAsInputStream("", "foo").trim(), "fooPOST");
|
||||
} catch (Exception e) {
|
||||
postFailures.incrementAndGet();
|
||||
@Test
|
||||
public void testZeroLengthPost() throws Exception {
|
||||
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
|
||||
public void testPostIsRetriedOnFailure() throws Exception {
|
||||
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().setContentLength(body.size());
|
||||
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"));
|
||||
} finally {
|
||||
if (payload != null) {
|
||||
payload.release();
|
||||
}
|
||||
assertTrue(postFailures.get() > 0, "expected failures");
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(invocationCount = 5, timeOut = 5000)
|
||||
public void testPostBinder() {
|
||||
assertEquals(client.postJson("", "foo").trim(), "{\"key\":\"foo\"}POST");
|
||||
@Test
|
||||
public void testPostContentEncoding() throws Exception {
|
||||
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().setContentLength(body.size());
|
||||
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"));
|
||||
} finally {
|
||||
if (payload != null) {
|
||||
payload.release();
|
||||
}
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(invocationCount = 5, timeOut = 5000)
|
||||
public void testPostContentDisposition() {
|
||||
Payload payload = newStringPayload("foo");
|
||||
payload.getContentMetadata().setContentDisposition("attachment; filename=photo.jpg");
|
||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||
assertEquals(headers.get("x-Content-Disposition"), ImmutableList.of("attachment; filename=photo.jpg"));
|
||||
payload.release();
|
||||
@Test
|
||||
public void testPostContentLanguage() throws Exception {
|
||||
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().setContentLength(body.size());
|
||||
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"));
|
||||
} finally {
|
||||
if (payload != null) {
|
||||
payload.release();
|
||||
}
|
||||
close(client, true);
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(invocationCount = 5, timeOut = 5000)
|
||||
public void testPostContentEncoding() {
|
||||
Payload payload = newStringPayload("foo");
|
||||
payload.getContentMetadata().setContentEncoding("gzip");
|
||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||
assertEquals(headers.get("x-Content-Encoding"), ImmutableList.of("gzip"));
|
||||
payload.release();
|
||||
@Test
|
||||
public void testPut() throws Exception {
|
||||
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)
|
||||
public void testPostContentLanguage() {
|
||||
Payload payload = newStringPayload("foo");
|
||||
payload.getContentMetadata().setContentLanguage("mi, en");
|
||||
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
|
||||
assertEquals(headers.get("x-Content-Language"), ImmutableList.of("mi, en"));
|
||||
payload.release();
|
||||
@Test
|
||||
public void testPutRedirect() throws Exception {
|
||||
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)
|
||||
public void testPut() {
|
||||
assertEquals(client.upload("", "foo").trim(), "fooPUT");
|
||||
@Test
|
||||
public void testZeroLengthPut() throws Exception {
|
||||
MockWebServer server = mockWebServer(new MockResponse());
|
||||
IntegrationTestClient client = client(server.getUrl("/").toString());
|
||||
try {
|
||||
client.putNothing("");
|
||||
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)
|
||||
public void testPutRedirect() {
|
||||
assertEquals(client.upload("redirect", "foo").trim(), "fooPUTREDIRECT");
|
||||
@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(invocationCount = 5, timeOut = 5000)
|
||||
public void testHead() {
|
||||
assertTrue(client.exists(""), "head returned false");
|
||||
@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(invocationCount = 5, timeOut = 5000)
|
||||
public void testGetAndParseSax() {
|
||||
assertEquals(client.downloadAndParse(""), "whoppers");
|
||||
@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(invocationCount = 5, timeOut = 5000)
|
||||
public void testZeroLengthPut() {
|
||||
client.putNothing("");
|
||||
@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
|
||||
@Path("/objects/{id}")
|
||||
ListenableFuture<Void> putNothing(@PathParam("id") String id);
|
||||
|
|
|
@ -198,6 +198,10 @@ public interface IntegrationTestClient extends Closeable {
|
|||
@PUT
|
||||
@Path("/objects/{id}")
|
||||
void putNothing(@PathParam("id") String id);
|
||||
|
||||
@POST
|
||||
@Path("/objects/{id}")
|
||||
void postNothing(@PathParam("id") String id);
|
||||
|
||||
@Provides
|
||||
StringBuilder newStringBuilder();
|
||||
|
|
|
@ -33,14 +33,14 @@ import com.google.inject.Module;
|
|||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Test
|
||||
@Test(groups = "integration")
|
||||
public class JavaUrlHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
||||
|
||||
protected Module createConnectionModule() {
|
||||
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_HOST, 0 + "");
|
||||
// IO workers not used in this executor
|
||||
|
|
|
@ -21,14 +21,15 @@ import static org.testng.Assert.assertTrue;
|
|||
|
||||
import java.io.IOException;
|
||||
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.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.http.IntegrationTestAsyncClient;
|
||||
import org.jclouds.http.IntegrationTestClient;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.providers.AnonymousProviderMetadata;
|
||||
import org.jclouds.reflect.Invocation;
|
||||
import org.jclouds.rest.internal.RestAnnotationProcessor;
|
||||
import org.testng.annotations.Test;
|
||||
|
@ -122,8 +123,10 @@ public class BackoffLimitedRetryHandlerTest {
|
|||
assertEquals(response.getPayload().getInput().read(), -1);
|
||||
}
|
||||
|
||||
private final Function<Invocation, HttpRequest> processor = BaseJettyTest.newBuilder(8100, new Properties()).buildInjector()
|
||||
.getInstance(RestAnnotationProcessor.class);
|
||||
private final Function<Invocation, HttpRequest> processor = ContextBuilder
|
||||
.newBuilder(AnonymousProviderMetadata.forApiOnEndpoint(IntegrationTestClient.class, "http://localhost"))
|
||||
.buildInjector().getInstance(RestAnnotationProcessor.class);
|
||||
|
||||
|
||||
private HttpCommand createCommand() throws SecurityException, NoSuchMethodException {
|
||||
Invokable<IntegrationTestAsyncClient, String> method = method(IntegrationTestAsyncClient.class, "download", String.class);
|
||||
|
|
|
@ -46,7 +46,7 @@ public class ApacheHCHttpCommandExecutorServiceTestDisabled extends BaseHttpComm
|
|||
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_HOST, 0 + "");
|
||||
props.setProperty(PROPERTY_CONNECTION_TIMEOUT, 100 + "");
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
<artifactId>appengine-api-1.0-sdk</artifactId>
|
||||
<version>1.6.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jclouds</groupId>
|
||||
<artifactId>jclouds-core</artifactId>
|
||||
|
@ -70,8 +69,8 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -19,16 +19,12 @@ package org.jclouds.gae;
|
|||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jclouds.concurrent.SingleThreaded;
|
||||
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
||||
import org.jclouds.gae.config.GoogleAppEngineConfigurationModule;
|
||||
import org.jclouds.http.BaseHttpCommandExecutorServiceIntegrationTest;
|
||||
import org.jclouds.http.HttpCommandExecutorService;
|
||||
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.testng.SkipException;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
@ -46,121 +42,31 @@ import com.google.inject.Module;
|
|||
*/
|
||||
@Test
|
||||
public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
|
||||
Logger logger = Logger.CONSOLE;
|
||||
|
||||
@Override
|
||||
protected void setupAndStartSSLServer(final int testPort) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean redirectEveryTwentyRequests(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
return false;
|
||||
@BeforeMethod
|
||||
public void setupApiProxy() {
|
||||
LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig());
|
||||
helper.setUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testPostAsInputStream() {
|
||||
throw new SkipException("streams aren't supported");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
||||
public void testPostBinder() {
|
||||
setupApiProxy();
|
||||
super.testPostBinder();
|
||||
public void testPostAsInputStreamDoesNotRetryOnFailure() throws Exception {
|
||||
throw new SkipException("streams aren't supported");
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
void setupApiProxy() {
|
||||
LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig());
|
||||
helper.setUp();
|
||||
|
||||
@Override
|
||||
public void testGetBigFile() {
|
||||
throw new SkipException("test data is too big for GAE");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
||||
public void testGetAndParseSax() {
|
||||
setupApiProxy();
|
||||
super.testGetAndParseSax();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
|
||||
public void testGetString() {
|
||||
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();
|
||||
public void testUploadBigFile() throws IOException {
|
||||
throw new SkipException("test data is too big for GAE");
|
||||
}
|
||||
|
||||
protected Module createConnectionModule() {
|
||||
|
@ -184,45 +90,8 @@ public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpC
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void addConnectionProperties(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();
|
||||
protected void addOverrideProperties(Properties props) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>netty</module>
|
||||
<module>enterprise</module>
|
||||
<module>okhttp</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -212,6 +212,7 @@
|
|||
<jclouds.test.listener>org.jclouds.test.testng.UnitTestStatusListener</jclouds.test.listener>
|
||||
<test.ssh.keyfile />
|
||||
<sourceReleaseAssemblyDescriptor>source-release-zip-tar</sourceReleaseAssemblyDescriptor>
|
||||
<okhttp.version>1.3.0</okhttp.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -251,6 +252,11 @@
|
|||
<artifactId>jetty-server</artifactId>
|
||||
<version>8.1.8.v20121106</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>${okhttp.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
|
|
Loading…
Reference in New Issue