diff --git a/codec-http2/pom.xml b/codec-http2/pom.xml
index f5e9c9cfb6..02914ebeb0 100644
--- a/codec-http2/pom.xml
+++ b/codec-http2/pom.xml
@@ -48,7 +48,6 @@
org.mockito
mockito-all
- 1.9.5
test
diff --git a/example/src/main/java/io/netty/example/http2/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/client/Http2Client.java
index b60f58b8ea..9203e1d0d6 100644
--- a/example/src/main/java/io/netty/example/http2/client/Http2Client.java
+++ b/example/src/main/java/io/netty/example/http2/client/Http2Client.java
@@ -28,6 +28,7 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
+import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.CharsetUtil;
@@ -55,8 +56,15 @@ public final class Http2Client {
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContext.newClientContext(
- null, InsecureTrustManagerFactory.INSTANCE, null,
+ null, InsecureTrustManagerFactory.INSTANCE,
+ Arrays.asList("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ // NOTE: Block ciphers are prohibited by the HTTP/2 specification
+ // http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-9.2.2.
+ // The following cipher exists to allow these examples to run with older JREs.
+ // Please consult the HTTP/2 specification when selecting cipher suites.
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"),
Arrays.asList(SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
+ JettyAlpnSslEngineWrapper.instance(),
0, 0);
} else {
sslCtx = null;
@@ -90,7 +98,8 @@ public final class Http2Client {
// Create a simple GET request.
FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, URL);
request.headers().add(HttpHeaders.Names.HOST, hostName);
- request.headers().add(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
+ // TODO: fix me when HTTP/2 supports decompression
+ // request.headers().add(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
channel.writeAndFlush(request);
responseHandler.put(streamId, channel.newPromise());
streamId += 2;
@@ -100,7 +109,8 @@ public final class Http2Client {
FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, URL2,
Unpooled.copiedBuffer(URL2DATA.getBytes(CharsetUtil.UTF_8)));
request.headers().add(HttpHeaders.Names.HOST, hostName);
- request.headers().add(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
+ // TODO: fix me when HTTP/2 supports decompression
+ // request.headers().add(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
channel.writeAndFlush(request);
responseHandler.put(streamId, channel.newPromise());
streamId += 2;
diff --git a/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java b/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java
index cfec8575c3..eb1e9519d5 100644
--- a/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java
+++ b/example/src/main/java/io/netty/example/http2/client/HttpResponseHandler.java
@@ -76,7 +76,13 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler 1) {
+ SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]);
+ System.err.println("Selected Protocol is " + selectedProtocol);
+ return selectedProtocol;
+ }
+ return SelectedProtocol.UNKNOWN;
}
@Override
diff --git a/example/src/main/java/io/netty/example/http2/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/server/Http2Server.java
index 467939c548..4fec25cd67 100644
--- a/example/src/main/java/io/netty/example/http2/server/Http2Server.java
+++ b/example/src/main/java/io/netty/example/http2/server/Http2Server.java
@@ -25,6 +25,7 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
@@ -45,10 +46,17 @@ public final class Http2Server {
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContext.newServerContext(
- ssc.certificate(), ssc.privateKey(), null, null,
+ ssc.certificate(), ssc.privateKey(), null,
+ Arrays.asList("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ // NOTE: Block ciphers are prohibited by the HTTP/2 specification
+ // http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-9.2.2.
+ // The following cipher exists to allow these examples to run with older JREs.
+ // Please consult the HTTP/2 specification when selecting cipher suites.
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"),
Arrays.asList(
SelectedProtocol.HTTP_2.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
+ JettyAlpnSslEngineWrapper.instance(),
0, 0);
} else {
sslCtx = null;
diff --git a/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java b/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java
index b6b042958a..95ea49dfe3 100644
--- a/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java
+++ b/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java
@@ -27,6 +27,7 @@ import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheObjectAggregator;
+import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@@ -50,6 +51,7 @@ public final class MemcacheClient {
sslCtx = SslContext.newClientContext(
null, InsecureTrustManagerFactory.INSTANCE, null,
Arrays.asList(SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
+ JettyAlpnSslEngineWrapper.instance(),
0, 0);
} else {
sslCtx = null;
diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java
index bccbe9e560..bccdf208d6 100644
--- a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java
+++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java
@@ -27,6 +27,7 @@ import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol;
+import io.netty.handler.ssl.JettyNpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@@ -56,6 +57,7 @@ public final class SpdyClient {
final SslContext sslCtx = SslContext.newClientContext(
null, InsecureTrustManagerFactory.INSTANCE, null,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
+ JettyNpnSslEngineWrapper.instance(),
0, 0);
HttpResponseClientHandler httpResponseHandler = new HttpResponseClientHandler();
diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java
index 991d727a22..05d9c69037 100644
--- a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java
+++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java
@@ -24,6 +24,7 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.ssl.JettyNpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
@@ -57,6 +58,7 @@ public final class SpdyServer {
SslContext sslCtx = SslContext.newServerContext(
ssc.certificate(), ssc.privateKey(), null, null,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
+ JettyNpnSslEngineWrapper.instance(),
0, 0);
// Configure the server.
diff --git a/handler/pom.xml b/handler/pom.xml
index aa06a88553..3e679f4df9 100644
--- a/handler/pom.xml
+++ b/handler/pom.xml
@@ -66,6 +66,22 @@
provided
true
+
+ org.eclipse.jetty.alpn
+ alpn-api
+ true
+
+
+ org.mortbay.jetty.alpn
+ alpn-boot
+ provided
+ true
+
+
+ org.mockito
+ mockito-all
+ test
+
diff --git a/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java b/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java
new file mode 100644
index 0000000000..c7db27c58a
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Factory for not wrapping {@link SSLEngine} object and just returning it
+ */
+public final class DefaultSslWrapperFactory implements SslEngineWrapperFactory {
+ private static final DefaultSslWrapperFactory INSTANCE = new DefaultSslWrapperFactory();
+
+ private DefaultSslWrapperFactory() {
+ }
+
+ public static DefaultSslWrapperFactory instance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public SSLEngine wrapSslEngine(SSLEngine engine, List protocols, boolean isServer) {
+ return engine;
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
index 07a0d1db98..99affaa2cd 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
@@ -19,19 +19,19 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
+import java.io.File;
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal;
-import java.io.File;
-import java.security.KeyStore;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
/**
* A client-side {@link SslContext} which uses JDK's SSL/TLS implementation.
@@ -45,7 +45,7 @@ public final class JdkSslClientContext extends JdkSslContext {
* Creates a new instance.
*/
public JdkSslClientContext() throws SSLException {
- this(null, null, null, null, 0, 0);
+ this(null, null);
}
/**
@@ -79,7 +79,7 @@ public final class JdkSslClientContext extends JdkSslContext {
* {@code null} to use the default.
*/
public JdkSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
- this(certChainFile, trustManagerFactory, null, null, 0, 0);
+ this(certChainFile, trustManagerFactory, null, null, DefaultSslWrapperFactory.instance(), 0, 0);
}
/**
@@ -94,6 +94,8 @@ public final class JdkSslClientContext extends JdkSslContext {
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
+ * @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
+ * This is required if {@code nextProtocols} is not {@code null} or empty
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@@ -102,26 +104,12 @@ public final class JdkSslClientContext extends JdkSslContext {
public JdkSslClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable ciphers, Iterable nextProtocols,
+ SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
- super(ciphers);
+ super(ciphers, wrapperFactory);
- if (nextProtocols != null && nextProtocols.iterator().hasNext()) {
- if (!JettyNpnSslEngine.isAvailable()) {
- throw new SSLException("NPN/ALPN unsupported: " + nextProtocols);
- }
-
- List nextProtoList = new ArrayList();
- for (String p: nextProtocols) {
- if (p == null) {
- break;
- }
- nextProtoList.add(p);
- }
- this.nextProtocols = Collections.unmodifiableList(nextProtoList);
- } else {
- this.nextProtocols = Collections.emptyList();
- }
+ this.nextProtocols = translateProtocols(nextProtocols);
try {
if (certChainFile == null) {
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
index 15402a8c2f..0ce4119a85 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
@@ -20,14 +20,15 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSessionContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSessionContext;
+
/**
* An {@link SslContext} which uses JDK's SSL/TLS implementation.
*/
@@ -110,10 +111,15 @@ public abstract class JdkSslContext extends SslContext {
private final String[] cipherSuites;
private final List unmodifiableCipherSuites;
+ private final SslEngineWrapperFactory wrapperFactory;
- JdkSslContext(Iterable ciphers) {
+ JdkSslContext(Iterable ciphers, SslEngineWrapperFactory wrapperFactory) {
+ if (wrapperFactory == null) {
+ throw new NullPointerException("wrapperFactory");
+ }
cipherSuites = toCipherSuiteArray(ciphers);
unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
+ this.wrapperFactory = wrapperFactory;
}
/**
@@ -166,11 +172,7 @@ public abstract class JdkSslContext extends SslContext {
}
private SSLEngine wrapEngine(SSLEngine engine) {
- if (nextProtocols().isEmpty()) {
- return engine;
- } else {
- return new JettyNpnSslEngine(engine, nextProtocols(), isServer());
- }
+ return wrapperFactory.wrapSslEngine(engine, nextProtocols(), isServer());
}
private static String[] toCipherSuiteArray(Iterable ciphers) {
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
index 34b08f29c3..955cc16a31 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
@@ -19,16 +19,6 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
-import javax.crypto.Cipher;
-import javax.crypto.EncryptedPrivateKeyInfo;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSessionContext;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
@@ -43,9 +33,20 @@ import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSessionContext;
+
/**
* A server-side {@link SslContext} which uses JDK's SSL/TLS implementation.
*/
@@ -73,7 +74,7 @@ public final class JdkSslServerContext extends JdkSslContext {
* {@code null} if it's not password-protected.
*/
public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
- this(certChainFile, keyFile, keyPassword, null, null, 0, 0);
+ this(certChainFile, keyFile, keyPassword, null, null, DefaultSslWrapperFactory.instance(), 0, 0);
}
/**
@@ -87,6 +88,8 @@ public final class JdkSslServerContext extends JdkSslContext {
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
+ * @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
+ * This is required if {@code nextProtocols} is not {@code null} or empty
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@@ -95,9 +98,12 @@ public final class JdkSslServerContext extends JdkSslContext {
public JdkSslServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable ciphers, Iterable nextProtocols,
+ SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
- super(ciphers);
+ super(ciphers, wrapperFactory);
+
+ this.nextProtocols = translateProtocols(nextProtocols);
if (certChainFile == null) {
throw new NullPointerException("certChainFile");
@@ -110,24 +116,6 @@ public final class JdkSslServerContext extends JdkSslContext {
keyPassword = "";
}
- if (nextProtocols != null && nextProtocols.iterator().hasNext()) {
- if (!JettyNpnSslEngine.isAvailable()) {
- throw new SSLException("NPN/ALPN unsupported: " + nextProtocols);
- }
-
- List list = new ArrayList();
- for (String p: nextProtocols) {
- if (p == null) {
- break;
- }
- list.add(p);
- }
-
- this.nextProtocols = Collections.unmodifiableList(list);
- } else {
- this.nextProtocols = Collections.emptyList();
- }
-
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java
new file mode 100644
index 0000000000..7a2b452343
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.alpn.ALPN.ClientProvider;
+import org.eclipse.jetty.alpn.ALPN.ServerProvider;
+
+final class JettyAlpnSslEngine extends JettySslEngine {
+ private static boolean available;
+
+ static boolean isAvailable() {
+ updateAvailability();
+ return available;
+ }
+
+ private static void updateAvailability() {
+ if (available) {
+ return;
+ }
+
+ try {
+ // Try to get the bootstrap class loader.
+ ClassLoader bootloader = ClassLoader.getSystemClassLoader().getParent();
+ if (bootloader == null) {
+ // If failed, use the system class loader,
+ // although it's not perfect to tell if APLN extension has been loaded.
+ bootloader = ClassLoader.getSystemClassLoader();
+ }
+ Class.forName("sun.security.ssl.ALPNExtension", true, bootloader);
+ available = true;
+ } catch (Exception ignore) {
+ // alpn-boot was not loaded.
+ }
+ }
+
+ JettyAlpnSslEngine(SSLEngine engine, final List nextProtocols, boolean server) {
+ super(engine, nextProtocols, server);
+
+ if (server) {
+ final String[] array = nextProtocols.toArray(new String[nextProtocols.size()]);
+ final String fallback = array[array.length - 1];
+
+ ALPN.put(engine, new ServerProvider() {
+ @Override
+ public String select(List protocols) {
+ for (int i = 0; i < array.length; ++i) {
+ String p = array[i];
+ if (protocols.contains(p)) {
+ session.setApplicationProtocol(p);
+ return p;
+ }
+ }
+ session.setApplicationProtocol(fallback);
+ return fallback;
+ }
+
+ @Override
+ public void unsupported() {
+ session.setApplicationProtocol(null);
+ }
+ });
+ } else {
+ ALPN.put(engine, new ClientProvider() {
+ @Override
+ public List protocols() {
+ return nextProtocols;
+ }
+
+ @Override
+ public void selected(String protocol) {
+ getSession().setApplicationProtocol(protocol);
+ }
+
+ @Override
+ public boolean supports() {
+ return true;
+ }
+
+ @Override
+ public void unsupported() {
+ getSession().setApplicationProtocol(nextProtocols.get(nextProtocols.size() - 1));
+ }
+ });
+ }
+ }
+
+ @Override
+ public void closeInbound() throws SSLException {
+ ALPN.remove(engine);
+ super.closeInbound();
+ }
+
+ @Override
+ public void closeOutbound() {
+ ALPN.remove(engine);
+ super.closeOutbound();
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngineWrapper.java b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngineWrapper.java
new file mode 100644
index 0000000000..a6d7aae7fa
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngineWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+
+/**
+ * Factory for wrapping {@link SSLEngine} object in {@link JettyAlpnSslEngine} objects
+ */
+public final class JettyAlpnSslEngineWrapper implements SslEngineWrapperFactory {
+ private static JettyAlpnSslEngineWrapper instance;
+
+ private JettyAlpnSslEngineWrapper() throws SSLException {
+ if (!JettyAlpnSslEngine.isAvailable()) {
+ throw new SSLException("ALPN unsupported. Is your classpatch configured correctly?" +
+ " See http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-starting");
+ }
+ }
+
+ public static JettyAlpnSslEngineWrapper instance() throws SSLException {
+ if (instance == null) {
+ instance = new JettyAlpnSslEngineWrapper();
+ }
+ return instance;
+ }
+
+ @Override
+ public SSLEngine wrapSslEngine(SSLEngine engine, List protocols, boolean isServer) {
+ return new JettyAlpnSslEngine(engine, protocols, isServer);
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java
index 49e9c3dc2d..cee6267bae 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java
@@ -16,21 +16,16 @@
package io.netty.handler.ssl;
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.npn.NextProtoNego.ClientProvider;
import org.eclipse.jetty.npn.NextProtoNego.ServerProvider;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLSession;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-final class JettyNpnSslEngine extends SSLEngine {
-
+final class JettyNpnSslEngine extends JettySslEngine {
private static boolean available;
static boolean isAvailable() {
@@ -57,14 +52,8 @@ final class JettyNpnSslEngine extends SSLEngine {
}
}
- private final SSLEngine engine;
- private final JettyNpnSslSession session;
-
JettyNpnSslEngine(SSLEngine engine, final List nextProtocols, boolean server) {
- assert !nextProtocols.isEmpty();
-
- this.engine = engine;
- session = new JettyNpnSslSession(engine);
+ super(engine, nextProtocols, server);
if (server) {
NextProtoNego.put(engine, new ServerProvider() {
@@ -84,8 +73,8 @@ final class JettyNpnSslEngine extends SSLEngine {
}
});
} else {
- final String[] list = nextProtocols.toArray(new String[nextProtocols.size()]);
- final String fallback = list[list.length - 1];
+ final String[] array = nextProtocols.toArray(new String[nextProtocols.size()]);
+ final String fallback = array[array.length - 1];
NextProtoNego.put(engine, new ClientProvider() {
@Override
@@ -100,181 +89,29 @@ final class JettyNpnSslEngine extends SSLEngine {
@Override
public String selectProtocol(List protocols) {
- for (String p: list) {
+ for (int i = 0; i < array.length; ++i) {
+ String p = array[i];
if (protocols.contains(p)) {
+ session.setApplicationProtocol(p);
return p;
}
}
+ session.setApplicationProtocol(fallback);
return fallback;
}
});
}
}
- @Override
- public JettyNpnSslSession getSession() {
- return session;
- }
-
@Override
public void closeInbound() throws SSLException {
NextProtoNego.remove(engine);
- engine.closeInbound();
+ super.closeInbound();
}
@Override
public void closeOutbound() {
NextProtoNego.remove(engine);
- engine.closeOutbound();
- }
-
- @Override
- public String getPeerHost() {
- return engine.getPeerHost();
- }
-
- @Override
- public int getPeerPort() {
- return engine.getPeerPort();
- }
-
- @Override
- public SSLEngineResult wrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException {
- return engine.wrap(byteBuffer, byteBuffer2);
- }
-
- @Override
- public SSLEngineResult wrap(ByteBuffer[] byteBuffers, ByteBuffer byteBuffer) throws SSLException {
- return engine.wrap(byteBuffers, byteBuffer);
- }
-
- @Override
- public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i2, ByteBuffer byteBuffer) throws SSLException {
- return engine.wrap(byteBuffers, i, i2, byteBuffer);
- }
-
- @Override
- public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException {
- return engine.unwrap(byteBuffer, byteBuffer2);
- }
-
- @Override
- public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers) throws SSLException {
- return engine.unwrap(byteBuffer, byteBuffers);
- }
-
- @Override
- public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i2) throws SSLException {
- return engine.unwrap(byteBuffer, byteBuffers, i, i2);
- }
-
- @Override
- public Runnable getDelegatedTask() {
- return engine.getDelegatedTask();
- }
-
- @Override
- public boolean isInboundDone() {
- return engine.isInboundDone();
- }
-
- @Override
- public boolean isOutboundDone() {
- return engine.isOutboundDone();
- }
-
- @Override
- public String[] getSupportedCipherSuites() {
- return engine.getSupportedCipherSuites();
- }
-
- @Override
- public String[] getEnabledCipherSuites() {
- return engine.getEnabledCipherSuites();
- }
-
- @Override
- public void setEnabledCipherSuites(String[] strings) {
- engine.setEnabledCipherSuites(strings);
- }
-
- @Override
- public String[] getSupportedProtocols() {
- return engine.getSupportedProtocols();
- }
-
- @Override
- public String[] getEnabledProtocols() {
- return engine.getEnabledProtocols();
- }
-
- @Override
- public void setEnabledProtocols(String[] strings) {
- engine.setEnabledProtocols(strings);
- }
-
- @Override
- public SSLSession getHandshakeSession() {
- return engine.getHandshakeSession();
- }
-
- @Override
- public void beginHandshake() throws SSLException {
- engine.beginHandshake();
- }
-
- @Override
- public HandshakeStatus getHandshakeStatus() {
- return engine.getHandshakeStatus();
- }
-
- @Override
- public void setUseClientMode(boolean b) {
- engine.setUseClientMode(b);
- }
-
- @Override
- public boolean getUseClientMode() {
- return engine.getUseClientMode();
- }
-
- @Override
- public void setNeedClientAuth(boolean b) {
- engine.setNeedClientAuth(b);
- }
-
- @Override
- public boolean getNeedClientAuth() {
- return engine.getNeedClientAuth();
- }
-
- @Override
- public void setWantClientAuth(boolean b) {
- engine.setWantClientAuth(b);
- }
-
- @Override
- public boolean getWantClientAuth() {
- return engine.getWantClientAuth();
- }
-
- @Override
- public void setEnableSessionCreation(boolean b) {
- engine.setEnableSessionCreation(b);
- }
-
- @Override
- public boolean getEnableSessionCreation() {
- return engine.getEnableSessionCreation();
- }
-
- @Override
- public SSLParameters getSSLParameters() {
- return engine.getSSLParameters();
- }
-
- @Override
- public void setSSLParameters(SSLParameters sslParameters) {
- engine.setSSLParameters(sslParameters);
+ super.closeOutbound();
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngineWrapper.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngineWrapper.java
new file mode 100644
index 0000000000..0fa05d91a3
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngineWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+
+/**
+ * Factory for wrapping {@link SSLEngine} object in {@link JettyNpnSslEngine} objects
+ */
+public final class JettyNpnSslEngineWrapper implements SslEngineWrapperFactory {
+ private static JettyNpnSslEngineWrapper instance;
+
+ private JettyNpnSslEngineWrapper() throws SSLException {
+ if (!JettyNpnSslEngine.isAvailable()) {
+ throw new SSLException("NPN unsupported. Is your classpatch configured correctly?" +
+ " See http://www.eclipse.org/jetty/documentation/current/npn-chapter.html#npn-starting");
+ }
+ }
+
+ public static JettyNpnSslEngineWrapper instance() throws SSLException {
+ if (instance == null) {
+ instance = new JettyNpnSslEngineWrapper();
+ }
+ return instance;
+ }
+
+ @Override
+ public SSLEngine wrapSslEngine(SSLEngine engine, List protocols, boolean isServer) {
+ return new JettyNpnSslEngine(engine, protocols, isServer);
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettySslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettySslEngine.java
new file mode 100644
index 0000000000..a72cf5648b
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JettySslEngine.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+
+class JettySslEngine extends SSLEngine {
+ protected final SSLEngine engine;
+ protected final JettySslSession session;
+
+ JettySslEngine(SSLEngine engine, final List nextProtocols, boolean server) {
+ if (nextProtocols == null) {
+ throw new NullPointerException("nextProtocols");
+ }
+ if (nextProtocols.isEmpty()) {
+ throw new IllegalArgumentException("nextProtocols can not be empty");
+ }
+
+ this.engine = engine;
+ session = new JettySslSession(engine);
+ }
+
+ @Override
+ public JettySslSession getSession() {
+ return session;
+ }
+
+ @Override
+ public void closeInbound() throws SSLException {
+ engine.closeInbound();
+ }
+
+ @Override
+ public void closeOutbound() {
+ engine.closeOutbound();
+ }
+
+ @Override
+ public String getPeerHost() {
+ return engine.getPeerHost();
+ }
+
+ @Override
+ public int getPeerPort() {
+ return engine.getPeerPort();
+ }
+
+ @Override
+ public SSLEngineResult wrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException {
+ return engine.wrap(byteBuffer, byteBuffer2);
+ }
+
+ @Override
+ public SSLEngineResult wrap(ByteBuffer[] byteBuffers, ByteBuffer byteBuffer) throws SSLException {
+ return engine.wrap(byteBuffers, byteBuffer);
+ }
+
+ @Override
+ public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i2, ByteBuffer byteBuffer) throws SSLException {
+ return engine.wrap(byteBuffers, i, i2, byteBuffer);
+ }
+
+ @Override
+ public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException {
+ return engine.unwrap(byteBuffer, byteBuffer2);
+ }
+
+ @Override
+ public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers) throws SSLException {
+ return engine.unwrap(byteBuffer, byteBuffers);
+ }
+
+ @Override
+ public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i2) throws SSLException {
+ return engine.unwrap(byteBuffer, byteBuffers, i, i2);
+ }
+
+ @Override
+ public Runnable getDelegatedTask() {
+ return engine.getDelegatedTask();
+ }
+
+ @Override
+ public boolean isInboundDone() {
+ return engine.isInboundDone();
+ }
+
+ @Override
+ public boolean isOutboundDone() {
+ return engine.isOutboundDone();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return engine.getSupportedCipherSuites();
+ }
+
+ @Override
+ public String[] getEnabledCipherSuites() {
+ return engine.getEnabledCipherSuites();
+ }
+
+ @Override
+ public void setEnabledCipherSuites(String[] strings) {
+ engine.setEnabledCipherSuites(strings);
+ }
+
+ @Override
+ public String[] getSupportedProtocols() {
+ return engine.getSupportedProtocols();
+ }
+
+ @Override
+ public String[] getEnabledProtocols() {
+ return engine.getEnabledProtocols();
+ }
+
+ @Override
+ public void setEnabledProtocols(String[] strings) {
+ engine.setEnabledProtocols(strings);
+ }
+
+ @Override
+ public SSLSession getHandshakeSession() {
+ return engine.getHandshakeSession();
+ }
+
+ @Override
+ public void beginHandshake() throws SSLException {
+ engine.beginHandshake();
+ }
+
+ @Override
+ public HandshakeStatus getHandshakeStatus() {
+ return engine.getHandshakeStatus();
+ }
+
+ @Override
+ public void setUseClientMode(boolean b) {
+ engine.setUseClientMode(b);
+ }
+
+ @Override
+ public boolean getUseClientMode() {
+ return engine.getUseClientMode();
+ }
+
+ @Override
+ public void setNeedClientAuth(boolean b) {
+ engine.setNeedClientAuth(b);
+ }
+
+ @Override
+ public boolean getNeedClientAuth() {
+ return engine.getNeedClientAuth();
+ }
+
+ @Override
+ public void setWantClientAuth(boolean b) {
+ engine.setWantClientAuth(b);
+ }
+
+ @Override
+ public boolean getWantClientAuth() {
+ return engine.getWantClientAuth();
+ }
+
+ @Override
+ public void setEnableSessionCreation(boolean b) {
+ engine.setEnableSessionCreation(b);
+ }
+
+ @Override
+ public boolean getEnableSessionCreation() {
+ return engine.getEnableSessionCreation();
+ }
+
+ @Override
+ public SSLParameters getSSLParameters() {
+ return engine.getSSLParameters();
+ }
+
+ @Override
+ public void setSSLParameters(SSLParameters sslParameters) {
+ engine.setSSLParameters(sslParameters);
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java b/handler/src/main/java/io/netty/handler/ssl/JettySslSession.java
similarity index 97%
rename from handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java
rename to handler/src/main/java/io/netty/handler/ssl/JettySslSession.java
index f4da3da1d5..845b1be6a0 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JettySslSession.java
@@ -24,12 +24,11 @@ import javax.security.cert.X509Certificate;
import java.security.Principal;
import java.security.cert.Certificate;
-final class JettyNpnSslSession implements SSLSession {
-
+final class JettySslSession implements SSLSession {
private final SSLEngine engine;
private volatile String applicationProtocol;
- JettyNpnSslSession(SSLEngine engine) {
+ JettySslSession(SSLEngine engine) {
this.engine = engine;
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
index a62c6d7b2d..f99e192395 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
@@ -65,7 +65,7 @@ public final class OpenSslServerContext extends SslContext {
private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers);
private final long sessionCacheSize;
private final long sessionTimeout;
- private final List nextProtocols;
+ private final List protocols;
/** The OpenSSL SSL_CTX object */
private final long ctx;
@@ -135,9 +135,8 @@ public final class OpenSslServerContext extends SslContext {
if (keyPassword == null) {
keyPassword = "";
}
- if (nextProtocols == null) {
- nextProtocols = Collections.emptyList();
- }
+
+ protocols = translateProtocols(nextProtocols);
for (String c: ciphers) {
if (c == null) {
@@ -146,15 +145,6 @@ public final class OpenSslServerContext extends SslContext {
this.ciphers.add(c);
}
- List nextProtoList = new ArrayList();
- for (String p: nextProtocols) {
- if (p == null) {
- break;
- }
- nextProtoList.add(p);
- }
- this.nextProtocols = Collections.unmodifiableList(nextProtoList);
-
// Allocate a new APR pool.
aprPool = Pool.create(0);
@@ -218,11 +208,11 @@ public final class OpenSslServerContext extends SslContext {
}
/* Set next protocols for next protocol negotiation extension, if specified */
- if (!nextProtoList.isEmpty()) {
+ if (!protocols.isEmpty()) {
// Convert the protocol list into a comma-separated string.
StringBuilder nextProtocolBuf = new StringBuilder();
- for (String p: nextProtoList) {
- nextProtocolBuf.append(p);
+ for (int i = 0; i < protocols.size(); ++i) {
+ nextProtocolBuf.append(protocols.get(i));
nextProtocolBuf.append(',');
}
nextProtocolBuf.setLength(nextProtocolBuf.length() - 1);
@@ -284,7 +274,7 @@ public final class OpenSslServerContext extends SslContext {
@Override
public List nextProtocols() {
- return nextProtocols;
+ return protocols;
}
/**
@@ -306,10 +296,10 @@ public final class OpenSslServerContext extends SslContext {
*/
@Override
public SSLEngine newEngine(ByteBufAllocator alloc) {
- if (nextProtocols.isEmpty()) {
+ if (protocols.isEmpty()) {
return new OpenSslEngine(ctx, alloc, null);
} else {
- return new OpenSslEngine(ctx, alloc, nextProtocols.get(nextProtocols.size() - 1));
+ return new OpenSslEngine(ctx, alloc, protocols.get(protocols.size() - 1));
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
index fde09e8d8d..7b04827fd7 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
@@ -25,7 +25,11 @@ import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
+
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
/**
@@ -51,7 +55,6 @@ import java.util.List;
*
*/
public abstract class SslContext {
-
/**
* Returns the default server-side implementation provider currently in use.
*
@@ -74,6 +77,28 @@ public abstract class SslContext {
return SslProvider.JDK;
}
+ /**
+ * Translate an {@link Iterable} of protocols to an unmodifiable {@link List}
+ * @param protocols Protocols to translate
+ * @return An unmodifiable {@link List} of protocols
+ */
+ public static List translateProtocols(Iterable protocols) {
+ Iterator itr = protocols == null ? null : protocols.iterator();
+ if (itr != null) {
+ List nextProtoList = new ArrayList(4);
+ while (itr.hasNext()) {
+ String p = itr.next();
+ if (p == null) {
+ break;
+ }
+ nextProtoList.add(p);
+ }
+ return Collections.unmodifiableList(nextProtoList);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
/**
* Creates a new server-side {@link SslContext}.
*
@@ -82,7 +107,7 @@ public abstract class SslContext {
* @return a new server-side {@link SslContext}
*/
public static SslContext newServerContext(File certChainFile, File keyFile) throws SSLException {
- return newServerContext(null, certChainFile, keyFile, null, null, null, 0, 0);
+ return newServerContext(certChainFile, keyFile, null);
}
/**
@@ -96,7 +121,7 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
File certChainFile, File keyFile, String keyPassword) throws SSLException {
- return newServerContext(null, certChainFile, keyFile, keyPassword, null, null, 0, 0);
+ return newServerContext(null, certChainFile, keyFile, keyPassword);
}
/**
@@ -110,6 +135,9 @@ public abstract class SslContext {
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
+ * @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
+ * This is required if {@code nextProtocols} is not {@code null} or empty
+ * and if OpenSSL is available
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@@ -119,10 +147,11 @@ public abstract class SslContext {
public static SslContext newServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable ciphers, Iterable nextProtocols,
+ SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newServerContext(
null, certChainFile, keyFile, keyPassword,
- ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
+ ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
}
/**
@@ -136,7 +165,7 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
SslProvider provider, File certChainFile, File keyFile) throws SSLException {
- return newServerContext(provider, certChainFile, keyFile, null, null, null, 0, 0);
+ return newServerContext(provider, certChainFile, keyFile, null);
}
/**
@@ -152,7 +181,8 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
SslProvider provider, File certChainFile, File keyFile, String keyPassword) throws SSLException {
- return newServerContext(provider, certChainFile, keyFile, keyPassword, null, null, 0, 0);
+ return newServerContext(provider, certChainFile, keyFile, keyPassword, null, null,
+ DefaultSslWrapperFactory.instance(), 0, 0);
}
/**
@@ -168,6 +198,9 @@ public abstract class SslContext {
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
+ * @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
+ * This is required if {@code nextProtocols} is not {@code null} or empty
+ * and if the {@code provider} is {@link SslProvider#JDK}
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@@ -178,6 +211,7 @@ public abstract class SslContext {
SslProvider provider,
File certChainFile, File keyFile, String keyPassword,
Iterable ciphers, Iterable nextProtocols,
+ SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider == null) {
@@ -188,7 +222,7 @@ public abstract class SslContext {
case JDK:
return new JdkSslServerContext(
certChainFile, keyFile, keyPassword,
- ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
+ ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
case OPENSSL:
return new OpenSslServerContext(
certChainFile, keyFile, keyPassword,
@@ -204,7 +238,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext() throws SSLException {
- return newClientContext(null, null, null, null, null, 0, 0);
+ return newClientContext(null, null, null);
}
/**
@@ -215,7 +249,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(File certChainFile) throws SSLException {
- return newClientContext(null, certChainFile, null, null, null, 0, 0);
+ return newClientContext(null, certChainFile);
}
/**
@@ -228,7 +262,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(TrustManagerFactory trustManagerFactory) throws SSLException {
- return newClientContext(null, null, trustManagerFactory, null, null, 0, 0);
+ return newClientContext(null, null, trustManagerFactory);
}
/**
@@ -244,7 +278,7 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
- return newClientContext(null, certChainFile, trustManagerFactory, null, null, 0, 0);
+ return newClientContext(null, certChainFile, trustManagerFactory);
}
/**
@@ -259,6 +293,9 @@ public abstract class SslContext {
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
+ * @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
+ * This is required if {@code nextProtocols} is not {@code null} or empty
+ * and if OpenSSL is available
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@@ -269,10 +306,11 @@ public abstract class SslContext {
public static SslContext newClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable ciphers, Iterable nextProtocols,
+ SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newClientContext(
null, certChainFile, trustManagerFactory,
- ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
+ ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
}
/**
@@ -284,7 +322,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(SslProvider provider) throws SSLException {
- return newClientContext(provider, null, null, null, null, 0, 0);
+ return newClientContext(provider, null, null);
}
/**
@@ -298,7 +336,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(SslProvider provider, File certChainFile) throws SSLException {
- return newClientContext(provider, certChainFile, null, null, null, 0, 0);
+ return newClientContext(provider, certChainFile, null);
}
/**
@@ -314,7 +352,7 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
SslProvider provider, TrustManagerFactory trustManagerFactory) throws SSLException {
- return newClientContext(provider, null, trustManagerFactory, null, null, 0, 0);
+ return newClientContext(provider, null, trustManagerFactory);
}
/**
@@ -332,7 +370,8 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
SslProvider provider, File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
- return newClientContext(provider, certChainFile, trustManagerFactory, null, null, 0, 0);
+ return newClientContext(provider, certChainFile, trustManagerFactory, null, null,
+ DefaultSslWrapperFactory.instance(), 0, 0);
}
/**
@@ -349,6 +388,9 @@ public abstract class SslContext {
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
+ * @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
+ * This is required if {@code nextProtocols} is not {@code null} or empty
+ * and if the {@code provider} is {@link SslProvider#JDK}
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@@ -360,6 +402,7 @@ public abstract class SslContext {
SslProvider provider,
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable ciphers, Iterable nextProtocols,
+ SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider != null && provider != SslProvider.JDK) {
@@ -368,7 +411,7 @@ public abstract class SslContext {
return new JdkSslClientContext(
certChainFile, trustManagerFactory,
- ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
+ ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
}
SslContext() { }
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslEngineWrapperFactory.java b/handler/src/main/java/io/netty/handler/ssl/SslEngineWrapperFactory.java
new file mode 100644
index 0000000000..8c413693fc
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/SslEngineWrapperFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Abstract factory pattern for wrapping an {@link SSLEngine} object.
+ * This is useful for NPN/APLN support.
+ */
+public interface SslEngineWrapperFactory {
+ /**
+ * Abstract factory pattern for wrapping an {@link SSLEngine} object.
+ * This is useful for NPN/APLN support.
+ *
+ * @param engine The engine to wrap
+ * @param protocols The application level protocols that are supported
+ * @param isServer
+ *
+ * - {@code true} if the engine is for server side of connections
+ * - {@code false} if the engine is for client side of connections
+ *
+ * @return The resulting wrapped engine. This may just be {@code engine}
+ */
+ SSLEngine wrapSslEngine(SSLEngine engine, List protocols, boolean isServer);
+}
diff --git a/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java
new file mode 100644
index 0000000000..149ec676a0
--- /dev/null
+++ b/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.ssl;
+
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.handler.ssl.util.SelfSignedCertificate;
+import io.netty.util.NetUtil;
+
+import java.net.InetSocketAddress;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class JettySslEngineTest {
+ private static final String APPLICATION_LEVEL_PROTOCOL = "my-protocol";
+
+ @Mock
+ private MessageReciever serverReceiver;
+ @Mock
+ private MessageReciever clientReceiver;
+
+ private SslContext serverSslCtx;
+ private SslContext clientSslCtx;
+ private ServerBootstrap sb;
+ private Bootstrap cb;
+ private Channel serverChannel;
+ private Channel serverConnectedChannel;
+ private Channel clientChannel;
+ private CountDownLatch serverLatch;
+ private CountDownLatch clientLatch;
+
+ private interface MessageReciever {
+ void messageReceived(ByteBuf msg);
+ }
+
+ private final class MessageDelegatorChannelHandler extends SimpleChannelInboundHandler {
+ private final MessageReciever receiver;
+
+ public MessageDelegatorChannelHandler(MessageReciever receiver) {
+ super(false);
+ this.receiver = receiver;
+ }
+
+ @Override
+ protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
+ receiver.messageReceived(msg);
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ serverLatch = new CountDownLatch(1);
+ clientLatch = new CountDownLatch(1);
+ }
+
+ @After
+ public void tearDown() throws InterruptedException {
+ if (serverChannel != null) {
+ serverChannel.close().sync();
+ sb.group().shutdownGracefully();
+ cb.group().shutdownGracefully();
+ }
+ clientChannel = null;
+ serverChannel = null;
+ serverConnectedChannel = null;
+ }
+
+ @Test
+ public void testNpn() throws Exception {
+ SslEngineWrapperFactory wrapper = null;
+ try {
+ wrapper = JettyNpnSslEngineWrapper.instance();
+ } catch (SSLException e) {
+ // NPN availability is dependent on the java version. If NPN is not available because of
+ // java version incompatibility don't fail the test, but instead just skip the test
+ assumeNoException(e);
+ }
+ mySetup(wrapper);
+ runTest();
+ }
+
+ @Test
+ public void testAlpn() throws Exception {
+ SslEngineWrapperFactory wrapper = null;
+ try {
+ wrapper = JettyAlpnSslEngineWrapper.instance();
+ } catch (SSLException e) {
+ // ALPN availability is dependent on the java version. If NPN is not available because of
+ // java version incompatibility don't fail the test, but instead just skip the test
+ assumeNoException(e);
+ }
+ mySetup(wrapper);
+ runTest();
+ }
+
+ private void mySetup(SslEngineWrapperFactory wrapper) throws InterruptedException, SSLException,
+ CertificateException {
+ SelfSignedCertificate ssc = new SelfSignedCertificate();
+ serverSslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey(), null, null,
+ Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
+ clientSslCtx = SslContext.newClientContext(SslProvider.JDK, null, InsecureTrustManagerFactory.INSTANCE, null,
+ Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
+
+ serverConnectedChannel = null;
+ sb = new ServerBootstrap();
+ cb = new Bootstrap();
+
+ sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
+ sb.channel(NioServerSocketChannel.class);
+ sb.childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel ch) throws Exception {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(serverSslCtx.newHandler(ch.alloc()));
+ p.addLast(new MessageDelegatorChannelHandler(serverReceiver));
+ serverConnectedChannel = ch;
+ }
+ });
+
+ cb.group(new NioEventLoopGroup());
+ cb.channel(NioSocketChannel.class);
+ cb.handler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel ch) throws Exception {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(clientSslCtx.newHandler(ch.alloc()));
+ p.addLast(new MessageDelegatorChannelHandler(clientReceiver));
+ }
+ });
+
+ serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
+ int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
+
+ ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
+ assertTrue(ccf.awaitUninterruptibly().isSuccess());
+ clientChannel = ccf.channel();
+ }
+
+ private void runTest() throws Exception {
+ ByteBuf clientMessage = Unpooled.copiedBuffer("I am a client".getBytes());
+ ByteBuf serverMessage = Unpooled.copiedBuffer("I am a server".getBytes());
+ try {
+ writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver);
+ writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver);
+ verifyApplicationLevelProtocol(clientChannel);
+ verifyApplicationLevelProtocol(serverConnectedChannel);
+ } finally {
+ clientMessage.release();
+ serverMessage.release();
+ }
+ }
+
+ private void verifyApplicationLevelProtocol(Channel channel) {
+ SslHandler handler = channel.pipeline().get(SslHandler.class);
+ assertNotNull(handler);
+ String[] protocol = handler.engine().getSession().getProtocol().split(":");
+ assertNotNull(protocol);
+ assertTrue("protocol.length must be greater than 1 but is " + protocol.length, protocol.length > 1);
+ assertEquals(APPLICATION_LEVEL_PROTOCOL, protocol[1]);
+ }
+
+ private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch,
+ MessageReciever receiver) throws Exception {
+ sendChannel.writeAndFlush(message);
+ receiverLatch.await(2, TimeUnit.SECONDS);
+ message.resetReaderIndex();
+ verify(receiver).messageReceived(eq(message));
+ }
+}
diff --git a/pom.xml b/pom.xml
index f297c8fee5..fbded08ed1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,7 +109,8 @@
false
- -D_
+ 8.0.0.v20140317
+ -Xbootclasspath/p:${jetty.alpn.path}
@@ -172,8 +173,9 @@
npn-7u9
@@ -272,7 +274,7 @@
- npn-7u40
+ npn-alpn-7u40
java.version
@@ -281,10 +283,12 @@
1.1.6.v20130911
+ 7.0.0.v20140317
+
- npn-7u45
+ npn-alpn-7u45
java.version
@@ -293,10 +297,12 @@
1.1.6.v20130911
+ 7.0.0.v20140317
+
- npn-7u51
+ npn-alpn-7u51
java.version
@@ -305,6 +311,81 @@
1.1.6.v20130911
+ 7.0.0.v20140317
+
+
+
+
+ npn-alpn-7u55
+
+
+ java.version
+ 1.7.0_55
+
+
+
+ 1.1.7.v20140316
+ 7.0.0.v20140317
+
+
+
+
+ npn-alpn-7u60
+
+
+ java.version
+ 1.7.0_60
+
+
+
+ 1.1.7.v20140316
+ 7.0.0.v20140317
+
+
+
+
+ npn-alpn-7u65
+
+
+ java.version
+ 1.7.0_65
+
+
+
+ 1.1.7.v20140316
+ 7.0.0.v20140317
+
+
+
+
+ npn-alpn-7u67
+
+
+ java.version
+ 1.7.0_67
+
+
+
+ 1.1.7.v20140316
+ 7.0.0.v20140317
+
+
+
+
+
+ forcenpn
+
+
+ forcenpn
+ true
+
+
+
+ -Xbootclasspath/p:${jetty.npn.path}
@@ -315,6 +396,8 @@
1.3.18.GA
1.1.7.v20140316
${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${jetty.npn.version}/npn-boot-${jetty.npn.version}.jar
+ 8.0.0.v20140317
+ ${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${jetty.alpn.version}/alpn-boot-${jetty.alpn.version}.jar
-server
-dsa -da -ea:io.netty...
@@ -325,7 +408,8 @@
-XX:+OptimizeStringConcat
-XX:+HeapDumpOnOutOfMemoryError
- -Xbootclasspath/p:${jetty.npn.path}
+
+ -Xbootclasspath/p:${jetty.alpn.path}
-verbose:gc
-D_
@@ -385,6 +469,16 @@
npn-boot
${jetty.npn.version}
+
+ org.eclipse.jetty.alpn
+ alpn-api
+ 1.0.0
+
+
+ org.mortbay.jetty.alpn
+ alpn-boot
+ ${jetty.alpn.version}
+
@@ -665,6 +759,14 @@
org.codehaus.mojo
animal-sniffer-maven-plugin
1.9
+
+
+
+ org.ow2.asm
+ asm-all
+ 5.0.3
+
+
org.codehaus.mojo.signature
@@ -754,6 +856,18 @@
${jetty.npn.version}
+
+ get-alpn-boot
+ validate
+
+ get
+
+
+ org.mortbay.jetty.alpn
+ alpn-boot
+ ${jetty.alpn.version}
+
+
@@ -791,7 +905,7 @@
${project.groupId}.*
- sun.misc.*;resolution:=optional,sun.nio.ch;resolution:=optional,sun.security.*;resolution:=optional,*
+ sun.misc.*;resolution:=optional,sun.nio.ch;resolution:=optional,sun.security.*;resolution:=optional,org.eclipse.jetty.alpn;version="[1,)";resolution=optional,*
!*
diff --git a/run-example.sh b/run-example.sh
index 3027bab000..a0cda4780c 100755
--- a/run-example.sh
+++ b/run-example.sh
@@ -40,9 +40,15 @@ EXAMPLE_MAP=(
'localecho:io.netty.example.localecho.LocalEcho'
)
+NEEDS_NPN_MAP=(
+ 'spdy-client'
+ 'spdy-server'
+)
+
EXAMPLE=''
EXAMPLE_CLASS=''
EXAMPLE_ARGS='-D_'
+FORCE_NPN=''
I=0
while [[ $# -gt 0 ]]; do
@@ -92,7 +98,13 @@ if [[ -z "$EXAMPLE" ]] || [[ -z "$EXAMPLE_CLASS" ]] || [[ $# -ne 0 ]]; then
exit 1
fi
+for E in "${NEEDS_NPN_MAP[@]}"; do
+ if [[ "$EXAMPLE" = "$E" ]]; then
+ FORCE_NPN='true'
+ break
+ fi
+done
+
cd "`dirname "$0"`"/example
echo "[INFO] Running: $EXAMPLE ($EXAMPLE_CLASS $EXAMPLE_ARGS)"
-exec mvn -q -nsu compile exec:exec -Dcheckstyle.skip=true -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS"
-
+exec mvn -q -nsu compile exec:exec -Dcheckstyle.skip=true -Dforcenpn="$FORCE_NPN" -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS"