Repository: relayrides/pushy
Pull request #395 (2016-12-11 01:23:10)

Allow callers to specify SSL providers
This change allows callers to specify an SSL provider for `ApnsClients` and `MockApnsServers`. It also adds `netty-tcnative` as an *unconditional* test dependency, and always uses the native provider (if available) for the mock server in tests.

This sets the stage for some token authentication changes, since we need to go from "required" to "optional" client TLS authentication, and the JDK and native SSL providers seem to treat failure there differently (TODO: isolate what's going on and file an upstream bug report).


Patch:
diff --git a/pushy/pom.xml b/pushy/pom.xml
index 0bf1582d..835925a0 100644
--- a/pushy/pom.xml
+++ b/pushy/pom.xml
@@ -53,6 +53,12 @@
             <version>1.7.6</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-tcnative-boringssl-static</artifactId>
+            <version>1.1.33.Fork24</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -83,7 +89,7 @@
                 </property>
             </activation>
             <properties>
-                <jetty.alpnAgent.version>2.0.0</jetty.alpnAgent.version>
+                <jetty.alpnAgent.version>2.0.5</jetty.alpnAgent.version>
                 <jetty.alpnAgent.path>${settings.localRepository}/org/mortbay/jetty/alpn/jetty-alpn-agent/${jetty.alpnAgent.version}/jetty-alpn-agent-${jetty.alpnAgent.version}.jar</jetty.alpnAgent.path>
                 <jetty.alpnAgent.option>forceNpn=false</jetty.alpnAgent.option>
                 <argLine.alpnAgent>-javaagent:${jetty.alpnAgent.path}=${jetty.alpnAgent.option}</argLine.alpnAgent>
@@ -119,34 +125,5 @@
                 </plugins>
             </build>
         </profile>
-        <profile>
-            <id>test-with-netty-tcnative</id>
-            <activation>
-                <property>
-                    <name>env.PUSHY_SSL_PROVIDER</name>
-                    <value>native</value>
-                </property>
-            </activation>
-            <dependencies>
-                <dependency>
-                    <groupId>io.netty</groupId>
-                    <artifactId>netty-tcnative-boringssl-static</artifactId>
-                    <version>1.1.33.Fork24</version>
-                    <scope>test</scope>
-                </dependency>
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-surefire-plugin</artifactId>
-                        <version>2.16</version>
-                        <configuration>
-                            <argLine>-Dorg.slf4j.simpleLogger.defaultLogLevel=warn</argLine>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
     </profiles>
 </project>
diff --git a/pushy/src/main/java/com/relayrides/pushy/apns/ApnsClientBuilder.java b/pushy/src/main/java/com/relayrides/pushy/apns/ApnsClientBuilder.java
index 290ad05c..dd52fe5f 100644
--- a/pushy/src/main/java/com/relayrides/pushy/apns/ApnsClientBuilder.java
+++ b/pushy/src/main/java/com/relayrides/pushy/apns/ApnsClientBuilder.java
@@ -72,6 +72,8 @@ public class ApnsClientBuilder {
     private InputStream trustedServerCertificateInputStream;
     private X509Certificate[] trustedServerCertificates;
 
+    private SslProvider preferredSslProvider;
+
     private EventLoopGroup eventLoopGroup;
 
     private ApnsClientMetricsListener metricsListener;
@@ -240,6 +242,22 @@ public class ApnsClientBuilder {
         return this;
     }
 
+    /**
+     * Sets the SSL provider to be used by the client under construction. By default, the client will use a native SSL
+     * provider if available and fall back to the JDK provider otherwise.
+     *
+     * @param sslProvider the SSL provider to be used by the client under construction, or {@code null} to choose a
+     * provider automatically
+     *
+     * @return a reference to this builder
+     *
+     * @since 0.9
+     */
+    public ApnsClientBuilder setSslProvider(final SslProvider sslProvider) {
+        this.preferredSslProvider = sslProvider;
+        return this;
+    }
+
     /**
      * <p>Sets the event loop group to be used by the client under construction. If not set (or if {@code null}), the
      * client will create and manage its own event loop group.</p>
@@ -393,17 +411,21 @@ public class ApnsClientBuilder {
         {
             final SslProvider sslProvider;
 
-            if (OpenSsl.isAvailable()) {
-                if (OpenSsl.isAlpnSupported()) {
-                    log.info("Native SSL provider is available and supports ALPN; will use native provider.");
-                    sslProvider = SslProvider.OPENSSL;
+            if (this.preferredSslProvider != null) {
+                sslProvider = this.preferredSslProvider;
+            } else {
+                if (OpenSsl.isAvailable()) {
+                    if (OpenSsl.isAlpnSupported()) {
+                        log.info("Native SSL provider is available and supports ALPN; will use native provider.");
+                        sslProvider = SslProvider.OPENSSL;
+                    } else {
+                        log.info("Native SSL provider is available, but does not support ALPN; will use JDK SSL provider.");
+                        sslProvider = SslProvider.JDK;
+                    }
                 } else {
-                    log.info("Native SSL provider is available, but does not support ALPN; will use JDK SSL provider.");
+                    log.info("Native SSL provider not available; will use JDK SSL provider.");
                     sslProvider = SslProvider.JDK;
                 }
-            } else {
-                log.info("Native SSL provider not available; will use JDK SSL provider.");
-                sslProvider = SslProvider.JDK;
             }
 
             final SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
diff --git a/pushy/src/main/java/com/relayrides/pushy/apns/MockApnsServerBuilder.java b/pushy/src/main/java/com/relayrides/pushy/apns/MockApnsServerBuilder.java
index 7c27f3ac..9ebe5410 100644
--- a/pushy/src/main/java/com/relayrides/pushy/apns/MockApnsServerBuilder.java
+++ b/pushy/src/main/java/com/relayrides/pushy/apns/MockApnsServerBuilder.java
@@ -72,6 +72,8 @@ public class MockApnsServerBuilder {
     private InputStream trustedClientCertificateInputStream;
     private X509Certificate[] trustedClientCertificates;
 
+    private SslProvider preferredSslProvider;
+
     private EventLoopGroup eventLoopGroup;
 
     private boolean emulateInternalErrors = false;
@@ -223,6 +225,21 @@ public class MockApnsServerBuilder {
         return this;
     }
 
+    /**
+     * Sets the SSL provider to be used by the mock server. By default, the server will use a native SSL provider if
+     * available and fall back to the JDK provider otherwise.
+     *
+     * @param sslProvider the SSL provider to be used by this server, or {@code null} to choose a provider automatically
+     *
+     * @return a reference to this builder
+     *
+     * @since 0.9
+     */
+    public MockApnsServerBuilder setSslProvider(final SslProvider sslProvider) {
+        this.preferredSslProvider = sslProvider;
+        return this;
+    }
+
     /**
      * <p>Sets the event loop group to be used by the server under construction. If not set (or if {@code null}), the
      * server will create and manage its own event loop group.</p>
@@ -269,17 +286,21 @@ public class MockApnsServerBuilder {
         {
             final SslProvider sslProvider;
 
-            if (OpenSsl.isAvailable()) {
-                if (OpenSsl.isAlpnSupported()) {
-                    log.info("Native SSL provider is available and supports ALPN; will use native provider.");
-                    sslProvider = SslProvider.OPENSSL;
+            if (this.preferredSslProvider != null) {
+                sslProvider = this.preferredSslProvider;
+            } else {
+                if (OpenSsl.isAvailable()) {
+                    if (OpenSsl.isAlpnSupported()) {
+                        log.info("Native SSL provider is available and supports ALPN; will use native provider.");
+                        sslProvider = SslProvider.OPENSSL;
+                    } else {
+                        log.info("Native SSL provider is available, but does not support ALPN; will use JDK SSL provider.");
+                        sslProvider = SslProvider.JDK;
+                    }
                 } else {
-                    log.info("Native SSL provider is available, but does not support ALPN; will use JDK SSL provider.");
+                    log.info("Native SSL provider not available; will use JDK SSL provider.");
                     sslProvider = SslProvider.JDK;
                 }
-            } else {
-                log.info("Native SSL provider not available; will use JDK SSL provider.");
-                sslProvider = SslProvider.JDK;
             }
 
             final SslContextBuilder sslContextBuilder;


Test patch:
diff --git a/pushy/src/test/java/com/relayrides/pushy/apns/ApnsClientTest.java b/pushy/src/test/java/com/relayrides/pushy/apns/ApnsClientTest.java
index 3227157b..3048add8 100644
--- a/pushy/src/test/java/com/relayrides/pushy/apns/ApnsClientTest.java
+++ b/pushy/src/test/java/com/relayrides/pushy/apns/ApnsClientTest.java
@@ -29,6 +29,7 @@ import com.relayrides.pushy.apns.util.SimpleApnsPushNotification;
 
 import io.netty.channel.WriteBufferWaterMark;
 import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.ssl.SslProvider;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GenericFutureListener;
 
@@ -58,6 +59,8 @@ public class ApnsClientTest {
     private MockApnsServer server;
     private ApnsClient client;
 
+    private SslProvider preferredSslProvider;
+
     private static class TestMetricsListener implements ApnsClientMetricsListener {
 
         private final List<Long> writeFailures = new ArrayList<>();
@@ -214,11 +217,14 @@ public class ApnsClientTest {
 
         this.server.start(PORT).await();
 
+        this.preferredSslProvider = "jdk".equals(System.getenv("PUSHY_SSL_PROVIDER")) ? SslProvider.JDK : null;
+
         try (final InputStream p12InputStream = ApnsClientTest.class.getResourceAsStream(SINGLE_TOPIC_CLIENT_KEYSTORE_FILENAME)) {
             this.client = new ApnsClientBuilder()
                     .setClientCredentials(p12InputStream, KEYSTORE_PASSWORD)
                     .setTrustedServerCertificateChain(CA_CERTIFICATE)
                     .setEventLoopGroup(EVENT_LOOP_GROUP)
+                    .setSslProvider(this.preferredSslProvider)
                     .build();
         }