okhttp: support JDK9 ALPN (#4136)

This commit is contained in:
Eric Gribkoff 2018-03-20 17:29:24 -07:00 committed by GitHub
parent 5241172475
commit 52fedb624d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 3 deletions

View File

@ -36,6 +36,8 @@ import org.junit.runners.Parameterized.Parameters;
/**
* Tests that Channel and Server builders properly hide the static constructors.
*
* <p>This test does nothing on Java 9.
*/
@RunWith(Parameterized.class)
public class ChannelAndServerBuilderTest {
@ -49,13 +51,19 @@ public class ChannelAndServerBuilderTest {
@Parameters(name = "class={0}")
public static Collection<Object[]> params() throws Exception {
ClassLoader loader = ChannelAndServerBuilderTest.class.getClassLoader();
Collection<ClassInfo> classInfos =
ClassPath.from(loader).getTopLevelClassesRecursive("io.grpc");
// Java 9 doesn't expose the URLClassLoader, which breaks searching through the classpath
if (classInfos.isEmpty()) {
return new ArrayList<Object[]>();
}
List<Object[]> classes = new ArrayList<Object[]>();
for (ClassInfo classInfo : ClassPath.from(loader).getTopLevelClassesRecursive("io.grpc")) {
for (ClassInfo classInfo : classInfos) {
Class<?> clazz = Class.forName(classInfo.getName(), false /*initialize*/, loader);
if (ServerBuilder.class.isAssignableFrom(clazz) && clazz != ServerBuilder.class) {
classes.add(new Object[]{clazz});
} else if (ManagedChannelBuilder.class.isAssignableFrom(clazz)
&& clazz != ManagedChannelBuilder.class ) {
&& clazz != ManagedChannelBuilder.class) {
classes.add(new Object[]{clazz});
}
}

View File

@ -19,6 +19,7 @@ package io.grpc.okhttp;
import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ -32,6 +33,7 @@ import io.grpc.okhttp.internal.Platform.TlsExtensionType;
import io.grpc.okhttp.internal.Protocol;
import java.io.IOException;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.junit.Rule;
@ -110,7 +112,9 @@ public class OkHttpProtocolNegotiatorTest {
@Test
public void negotiate_handshakeFails() throws IOException {
SSLParameters parameters = new SSLParameters();
OkHttpProtocolNegotiator negotiator = OkHttpProtocolNegotiator.get();
doReturn(parameters).when(sock).getSSLParameters();
doThrow(new IOException()).when(sock).startHandshake();
thrown.expect(IOException.class);

View File

@ -29,7 +29,11 @@ import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.security.AccessController;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
@ -37,6 +41,8 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import okio.Buffer;
@ -44,19 +50,25 @@ import okio.Buffer;
* Access to platform-specific features.
*
* <h3>Server name indication (SNI)</h3>
*
* Supported on Android 2.3+.
*
* <h3>Session Tickets</h3>
*
* Supported on Android 2.3+.
*
* <h3>Android Traffic Stats (Socket Tagging)</h3>
*
* Supported on Android 4.0+.
*
* <h3>ALPN (Application Layer Protocol Negotiation)</h3>
*
* Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
* unstable.
*
* Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
* <p>Supported on OpenJDK 9+.
*
* <p>Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
*/
public class Platform {
public static final Logger logger = Logger.getLogger(Platform.class.getName());
@ -199,6 +211,47 @@ public class Platform {
throw new RuntimeException(nsae);
}
// Find JDK9+ ALPN support
try {
// getApplicationProtocol() may throw UnsupportedOperationException, so first construct a
// dummy SSLEngine and verify the method does not throw.
SSLContext context = SSLContext.getInstance("TLS", sslProvider);
context.init(null, null, null);
SSLEngine engine = context.createSSLEngine();
Method getEngineApplicationProtocol =
AccessController.doPrivileged(
new PrivilegedExceptionAction<Method>() {
@Override
public Method run() throws Exception {
return SSLEngine.class.getMethod("getApplicationProtocol");
}
});
getEngineApplicationProtocol.invoke(engine);
Method setApplicationProtocols =
AccessController.doPrivileged(
new PrivilegedExceptionAction<Method>() {
@Override
public Method run() throws Exception {
return SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
}
});
Method getApplicationProtocol =
AccessController.doPrivileged(
new PrivilegedExceptionAction<Method>() {
@Override
public Method run() throws Exception {
return SSLSocket.class.getMethod("getApplicationProtocol");
}
});
return new JdkAlpnPlatform(sslProvider, setApplicationProtocols, getApplicationProtocol);
} catch (NoSuchAlgorithmException ignored) {
} catch (KeyManagementException ignored) {
} catch (PrivilegedActionException ignored) {
} catch (IllegalAccessException ignored) {
} catch (InvocationTargetException ignored) {
}
// Find Jetty's ALPN extension for OpenJDK.
try {
String negoClassName = "org.eclipse.jetty.alpn.ALPN";
@ -375,6 +428,56 @@ public class Platform {
}
}
/** OpenJDK 9+. */
private static class JdkAlpnPlatform extends Platform {
private final Method setApplicationProtocols;
private final Method getApplicationProtocol;
private JdkAlpnPlatform(
Provider provider, Method setApplicationProtocols, Method getApplicationProtocol) {
super(provider);
this.setApplicationProtocols = setApplicationProtocols;
this.getApplicationProtocol = getApplicationProtocol;
}
@Override
public TlsExtensionType getTlsExtensionType() {
return TlsExtensionType.ALPN_AND_NPN;
}
@Override
public void configureTlsExtensions(
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
SSLParameters parameters = sslSocket.getSSLParameters();
List<String> names = new ArrayList<String>(protocols.size());
for (Protocol protocol : protocols) {
if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
names.add(protocol.toString());
}
try {
setApplicationProtocols.invoke(
parameters, new Object[] {names.toArray(new String[names.size()])});
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
sslSocket.setSSLParameters(parameters);
}
/** Returns the negotiated protocol, or null if no protocol was negotiated. */
@Override
public String getSelectedProtocol(SSLSocket socket) {
try {
return (String) getApplicationProtocol.invoke(socket);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/**
* OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
*/