Skip to content

Commit

Permalink
JCLOUDS-753: Make ConnectionSpec configurable in the OkHttp driver
Browse files Browse the repository at this point in the history
  • Loading branch information
nacx committed Dec 2, 2014
1 parent c635b30 commit 958d09e
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,19 @@ protected static MockWebServer mockWebServer(Dispatcher dispatcher) throws IOExc
* Creates a test api for the given class and URL.
*/
protected <T extends Closeable> T api(Class<T> apiClass, String url) {
return api(apiClass, url, createConnectionModule());
}

/**
* Creates a test api for the given class, URI and Module.
*/
protected <T extends Closeable> T api(Class<T> apiClass, String url, Module... connectionModules) {
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);
.modules(ImmutableSet.copyOf(connectionModules)).overrides(properties).buildApi(apiClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

import org.jclouds.http.okhttp.OkHttpClientSupplier.NewOkHttpClient;

import com.google.common.annotations.Beta;
import com.google.common.base.Supplier;
import com.google.inject.ImplementedBy;
import com.squareup.okhttp.OkHttpClient;

/**
* Provides the OkHttp client used for all requests. This could be used to
* designate a custom SSL context or limit TLS ciphers.
* <p>
* Note that it should configured it in the Guice module designated as
* <code>@ConfiguresHttpApi</code>.
*/
@Beta
@ImplementedBy(NewOkHttpClient.class)
public interface OkHttpClientSupplier extends Supplier<OkHttpClient> {

static final class NewOkHttpClient implements OkHttpClientSupplier {
@Override
public OkHttpClient get() {
return new OkHttpClient();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

public final class OkHttpCommandExecutorService extends BaseHttpCommandExecutorService<Request> {

private static final String DEFAULT_USER_AGENT = String.format("jclouds/%s java/%s", JcloudsVersion.get(),
private static final String DEFAULT_USER_AGENT = String.format("jclouds-okhttp/%s java/%s", JcloudsVersion.get(),
System.getProperty("java.version"));

private final Function<URI, Proxy> proxyForURI;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.jclouds.http.HttpUtils;
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
import org.jclouds.http.config.SSLModule;
import org.jclouds.http.okhttp.OkHttpClientSupplier;
import org.jclouds.http.okhttp.OkHttpCommandExecutorService;

import com.google.common.base.Supplier;
Expand All @@ -51,24 +52,23 @@ protected void configure() {
}

private static final class OkHttpClientProvider implements Provider<OkHttpClient> {

@Inject(optional = true)
private Supplier<SSLContext> sslContextSupplier;
private final HostnameVerifier verifier;
private final Supplier<SSLContext> untrustedSSLContextProvider;
private final HttpUtils utils;
private final OkHttpClientSupplier clientSupplier;

@Inject
OkHttpClientProvider(HttpUtils utils, @Named("untrusted") HostnameVerifier verifier,
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider) {
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider, OkHttpClientSupplier clientSupplier) {
this.utils = utils;
this.verifier = verifier;
this.untrustedSSLContextProvider = untrustedSSLContextProvider;
this.clientSupplier = clientSupplier;
}

@Override
public OkHttpClient get() {
OkHttpClient client = new OkHttpClient();
OkHttpClient client = clientSupplier.get();
client.setConnectTimeout(utils.getConnectionTimeout(), TimeUnit.MILLISECONDS);
client.setReadTimeout(utils.getSocketOpenTimeout(), TimeUnit.MILLISECONDS);
// do not follow redirects since https redirects don't work properly
Expand All @@ -79,18 +79,12 @@ public OkHttpClient get() {
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()) {
if (utils.trustAllCerts()) {
client.setSslSocketFactory(untrustedSSLContextProvider.get().getSocketFactory());
}

return client;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,28 @@
import static org.testng.Assert.assertEquals;

import java.io.Closeable;
import java.util.List;
import java.util.Properties;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

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.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.squareup.okhttp.ConnectionSpec;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.TlsVersion;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
Expand Down Expand Up @@ -149,4 +157,91 @@ public void testZeroLengthPatch() throws Exception {
server.shutdown();
}
}

@Test(expectedExceptions = HttpResponseException.class, expectedExceptionsMessageRegExp = ".*exhausted connection specs.*")
public void testSSLConnectionFailsIfOnlyHttpConfigured() throws Exception {
MockWebServer server = mockWebServer(new MockResponse());
server.useHttps(sslContext.getSocketFactory(), false);
Module httpConfigModule = new ConnectionSpecModule(ConnectionSpec.CLEARTEXT);
PatchApi api = api(PatchApi.class, server.getUrl("/").toString(), httpConfigModule);
try {
api.patchNothing("");
} finally {
closeQuietly(api);
server.shutdown();
}
}

@Test(expectedExceptions = HttpResponseException.class, expectedExceptionsMessageRegExp = ".*exhausted connection specs.*")
public void testHTTPConnectionFailsIfOnlySSLConfigured() throws Exception {
MockWebServer server = mockWebServer(new MockResponse());
Module httpConfigModule = new ConnectionSpecModule(ConnectionSpec.MODERN_TLS);
PatchApi api = api(PatchApi.class, server.getUrl("/").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.getUrl("/").toString()));
server.useHttps(sslContext.getSocketFactory(), false);
Module httpConfigModule = new ConnectionSpecModule(ConnectionSpec.CLEARTEXT, ConnectionSpec.MODERN_TLS);
PatchApi api = api(PatchApi.class, server.getUrl("/").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(sslContext.getSocketFactory(), false);
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS).tlsVersions(TlsVersion.TLS_1_2)
.build();
PatchApi api = api(PatchApi.class, server.getUrl("/").toString(), new ConnectionSpecModule(spec));
try {
api.patchNothing("");
assertEquals(server.getRequestCount(), 1);
RecordedRequest request = server.takeRequest();
assertEquals(request.getSslProtocol(), "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() {
OkHttpClient client = new OkHttpClient();
client.setConnectionSpecs(connectionSpecs);
return client;
}
});
}
}

}

0 comments on commit 958d09e

Please sign in to comment.