Added OkHttp driver to support modern HTTP verbs

This commit is contained in:
Ignasi Barrera 2013-12-14 00:26:57 +01:00
parent 35ecf794cf
commit 09a430744a
21 changed files with 1221 additions and 399 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
} }
} }

View File

@ -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;
}
} }

View File

@ -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();
}

View File

@ -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);

View File

@ -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();
} }

View File

@ -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

View File

@ -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);

View File

@ -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 + "");

View File

@ -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>

View File

@ -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();
} }
} }

15
drivers/okhttp/README.md Normal file
View File

@ -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();

64
drivers/okhttp/pom.xml Normal file
View File

@ -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>

View File

@ -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, "*/*");
}
}
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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>

View File

@ -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>