From a8e2f930ea2be9393a12d0000ecd966086488a78 Mon Sep 17 00:00:00 2001 From: jwilson Date: Sun, 12 Jan 2014 22:39:04 -0500 Subject: [PATCH] Support ALPN on Android 4.4+. ALPN is the successor to NPN. When it's widely deployed we can drop support for NPN altogether. Verified manually on my Android device against https://gmail.com (which uses ALPN) and https://twitter.com (which uses NPN only). https://github.com/square/okhttp/issues/128 --- .../squareup/okhttp/internal/Platform.java | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java index 5aa46067c..33c120d16 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java @@ -41,11 +41,25 @@ import javax.net.ssl.SSLSocket; /** * Access to Platform-specific features necessary for SPDY and advanced TLS. * - *

SPDY

- * SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's - * available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It - * also requires a recent version of {@code DeflaterOutputStream} that is - * public API in Java 7 and callable via reflection in Android 4.1+. + *

ALPN and NPN

+ * This class uses TLS extensions ALPN and NPN to negotiate the upgrade from + * HTTP/1.1 (the default protocol to use with TLS on port 443) to either SPDY + * or HTTP/2.0. + * + *

NPN (Next Protocol Negotiation) was developed for SPDY. It is widely + * available and we support it on both Android (4.1+) and OpenJDK 7 (via the + * Jetty NPN-boot library). + * + *

ALPN (Application Layer Protocol Negotiation) is the successor to NPN. It + * has some technical advantages over NPN. We support it on Android (4.4+) only. + * + *

On platforms that support both extensions, OkHttp will use both, + * preferring ALPN's result. Future versions of OkHttp will drop support NPN. + * + *

Deflater Sync Flush

+ * SPDY header compression requires a recent version of {@code + * DeflaterOutputStream} that is public API in Java 7 and callable via + * reflection in Android 4.1+. */ public class Platform { private static final Platform PLATFORM = findPlatform(); @@ -155,14 +169,21 @@ public class Platform { // Attempt to find Android 4.1+ APIs. Method setNpnProtocols = null; Method getNpnSelectedProtocol = null; + Method setAlpnProtocols = null; + Method getAlpnSelectedProtocol = null; try { setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class); getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol"); + try { + setAlpnProtocols = openSslSocketClass.getMethod("setAlpnProtocols", byte[].class); + getAlpnSelectedProtocol = openSslSocketClass.getMethod("getAlpnSelectedProtocol"); + } catch (NoSuchMethodException ignored) { + } } catch (NoSuchMethodException ignored) { } return new Android(openSslSocketClass, setUseSessionTickets, setHostname, setNpnProtocols, - getNpnSelectedProtocol); + getNpnSelectedProtocol, setAlpnProtocols, getAlpnSelectedProtocol); } catch (ClassNotFoundException ignored) { // This isn't an Android runtime. } catch (NoSuchMethodException ignored) { @@ -199,18 +220,25 @@ public class Platform { private final Method setUseSessionTickets; private final Method setHostname; - // Non-null on Android 4.1+ + // Non-null on Android 4.1+. private final Method setNpnProtocols; private final Method getNpnSelectedProtocol; + // Non-null on Android 4.4+. + private final Method setAlpnProtocols; + private final Method getAlpnSelectedProtocol; + private Android( Class openSslSocketClass, Method setUseSessionTickets, Method setHostname, - Method setNpnProtocols, Method getNpnSelectedProtocol) { + Method setNpnProtocols, Method getNpnSelectedProtocol, Method setAlpnProtocols, + Method getAlpnSelectedProtocol) { this.openSslSocketClass = openSslSocketClass; this.setUseSessionTickets = setUseSessionTickets; this.setHostname = setHostname; this.setNpnProtocols = setNpnProtocols; this.getNpnSelectedProtocol = getNpnSelectedProtocol; + this.setAlpnProtocols = setAlpnProtocols; + this.getAlpnSelectedProtocol = getAlpnSelectedProtocol; } @Override public void connectSocket(Socket socket, InetSocketAddress address, @@ -243,7 +271,11 @@ public class Platform { if (setNpnProtocols == null) return; if (!openSslSocketClass.isInstance(socket)) return; try { - setNpnProtocols.invoke(socket, new Object[] {npnProtocols}); + Object[] parameters = { npnProtocols }; + if (setAlpnProtocols != null) { + setAlpnProtocols.invoke(socket, parameters); + } + setNpnProtocols.invoke(socket, parameters); } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { @@ -255,6 +287,11 @@ public class Platform { if (getNpnSelectedProtocol == null) return null; if (!openSslSocketClass.isInstance(socket)) return null; try { + if (getAlpnSelectedProtocol != null) { + // Prefer ALPN's result if it is present. + byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invoke(socket); + if (alpnResult != null) return alpnResult; + } return (byte[]) getNpnSelectedProtocol.invoke(socket); } catch (InvocationTargetException e) { throw new RuntimeException(e);