blob: 877cf88fd7c8458561ee22380987a758fe789f37 [file] [log] [blame]
/*
* 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 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.jclouds.util.Closeables2.closeQuietly;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.io.Closeable;
import java.util.List;
import java.util.Properties;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.jclouds.http.BaseHttpCommandExecutorServiceIntegrationTest;
import org.jclouds.http.HttpResponseException;
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
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.common.collect.ImmutableList;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
/**
* Tests the functionality of the {@link OkHttpCommandExecutorService}
*/
@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 + "");
props.setProperty(PROPERTY_USER_THREADS, 5 + "");
}
private interface PatchApi extends Closeable {
@PATCH
@Path("/objects/{id}")
@Produces("text/plain")
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.url("/").toString());
try {
String result = api.patch("", "foo");
// Verify that the body is properly populated
RecordedRequest request = server.takeRequest();
assertEquals(request.getMethod(), "PATCH");
assertEquals(request.getBody().readUtf8(), "foo");
assertEquals(result, "fooPATCH");
// Verify content headers are sent
assertNotNull(request.getHeader("Content-Type"));
assertNotNull(request.getHeader("Content-Length"));
assertEquals(request.getHeader("Content-Type"), "text/plain");
assertEquals(Integer.valueOf(request.getHeader("Content-Length")).intValue(), "foo".getBytes().length);
} finally {
closeQuietly(api);
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.url("/").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(request.getBody().readUtf8(), "foo");
request = server.takeRequest();
assertEquals(request.getMethod(), "PATCH");
assertEquals(request.getBody().readUtf8(), "foo");
} finally {
closeQuietly(api);
server.shutdown();
}
}
@Test
public void testPatchRedirect() throws Exception {
MockWebServer redirectTarget = mockWebServer(new MockResponse().setBody("fooPATCHREDIRECT"));
redirectTarget.useHttps(sslSocketFactory(), false);
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(302).setHeader("Location",
redirectTarget.url("/").toString()));
PatchApi api = api(PatchApi.class, server.url("/").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(request.getBody().readUtf8(), "foo");
request = redirectTarget.takeRequest();
assertEquals(request.getMethod(), "PATCH");
assertEquals(request.getBody().readUtf8(), "foo");
} finally {
closeQuietly(api);
redirectTarget.shutdown();
server.shutdown();
}
}
@Test
public void testZeroLengthPatch() throws Exception {
MockWebServer server = mockWebServer(new MockResponse());
PatchApi api = api(PatchApi.class, server.url("/").toString());
try {
api.patchNothing("");
assertEquals(server.getRequestCount(), 1);
RecordedRequest request = server.takeRequest();
assertEquals(request.getMethod(), "PATCH");
assertEquals(request.getBody().readUtf8(), "");
} finally {
closeQuietly(api);
server.shutdown();
}
}
@Test(expectedExceptions = HttpResponseException.class, expectedExceptionsMessageRegExp = "Failed to connect to.*")
public void testSSLConnectionFailsIfOnlyHttpConfigured() throws Exception {
MockWebServer server = mockWebServer(new MockResponse());
server.useHttps(sslSocketFactory(), false);
Module httpConfigModule = new ConnectionSpecModule(ConnectionSpec.CLEARTEXT);
PatchApi api = api(PatchApi.class, server.url("/").toString(), httpConfigModule);
try {
api.patchNothing("");
} finally {
closeQuietly(api);
server.shutdown();
}
}
@Test(expectedExceptions = HttpResponseException.class, expectedExceptionsMessageRegExp = "CLEARTEXT communication not enabled for client.*")
public void testHTTPConnectionFailsIfOnlySSLConfigured() throws Exception {
MockWebServer server = mockWebServer(new MockResponse());
Module httpConfigModule = new ConnectionSpecModule(ConnectionSpec.MODERN_TLS);
PatchApi api = api(PatchApi.class, server.url("/").toString(), httpConfigModule);
try {
api.patchNothing("");
} finally {
closeQuietly(api);
server.shutdown();
}
}
@Test
public void testBothProtocolsSucceedIfSSLAndHTTPConfigured() throws Exception {
MockWebServer redirectTarget = mockWebServer(new MockResponse());
MockWebServer server = mockWebServer(new MockResponse().setResponseCode(302).setHeader("Location",
redirectTarget.url("/").toString()));
server.useHttps(sslSocketFactory(), false);
Module httpConfigModule = new ConnectionSpecModule(ConnectionSpec.CLEARTEXT, ConnectionSpec.MODERN_TLS);
PatchApi api = api(PatchApi.class, server.url("/").toString(), httpConfigModule);
try {
api.patchNothing("");
assertEquals(server.getRequestCount(), 1);
assertEquals(redirectTarget.getRequestCount(), 1);
} finally {
closeQuietly(api);
server.shutdown();
redirectTarget.shutdown();
}
}
@Test
public void testRestrictedSSLProtocols() throws Exception {
MockWebServer server = mockWebServer(new MockResponse());
server.useHttps(sslSocketFactory(), false);
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS).tlsVersions(TlsVersion.TLS_1_2)
.build();
PatchApi api = api(PatchApi.class, server.url("/").toString(), new ConnectionSpecModule(spec));
try {
api.patchNothing("");
assertEquals(server.getRequestCount(), 1);
RecordedRequest request = server.takeRequest();
assertEquals(request.getTlsVersion().javaName(), "TLSv1.2");
} finally {
closeQuietly(api);
server.shutdown();
}
}
@ConfiguresHttpCommandExecutorService
private static final class ConnectionSpecModule extends AbstractModule {
private final List<ConnectionSpec> connectionSpecs;
public ConnectionSpecModule(ConnectionSpec... connectionSpecs) {
this.connectionSpecs = ImmutableList.copyOf(connectionSpecs);
}
@Override
protected void configure() {
install(new OkHttpCommandExecutorServiceModule());
bind(OkHttpClientSupplier.class).toInstance(new OkHttpClientSupplier() {
@Override
public OkHttpClient get() {
return new OkHttpClient.Builder()
.connectionSpecs(connectionSpecs)
.build();
}
});
}
}
}