Skip to content

Commit

Permalink
Merge pull request square#440 from square/jwilson_0112_alpn
Browse files Browse the repository at this point in the history
Support ALPN on Android 4.4+.
  • Loading branch information
JakeWharton committed Jan 13, 2014
2 parents 0c008f3 + a8e2f93 commit de84b28
Showing 1 changed file with 46 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,25 @@
/**
* Access to Platform-specific features necessary for SPDY and advanced TLS.
*
* <h3>SPDY</h3>
* 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+.
* <h3>ALPN and NPN</h3>
* 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.
*
* <p>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).
*
* <p>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.
*
* <p>On platforms that support both extensions, OkHttp will use both,
* preferring ALPN's result. Future versions of OkHttp will drop support NPN.
*
* <h3>Deflater Sync Flush</h3>
* 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();
Expand Down Expand Up @@ -155,14 +169,21 @@ private static Platform findPlatform() {
// 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) {
Expand Down Expand Up @@ -199,18 +220,25 @@ private static class Android extends 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,
Expand Down Expand Up @@ -243,7 +271,11 @@ private Android(
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) {
Expand All @@ -255,6 +287,11 @@ private Android(
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);
Expand Down

0 comments on commit de84b28

Please sign in to comment.