Repository: netty/netty
Issue: Feature request: Websocket SSL client example
Labels: feature
help wanted

It would be nice if a working SSL websocket client example could be added to the examples.


Issue comments:
Was fixed by #2266


Pull request review comments:
Why this license header ?

why copied at all and not reused ?

maybe some javadocs how to use it ?

Should be final


Patch that resolved the issue:
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java
index 5e6095167f3..6224c45ad17 100644
--- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java
+++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 The Netty Project
+ * 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
@@ -13,123 +13,35 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
-//The MIT License
-//
-//Copyright (c) 2009 Carl Bystršm
-//
-//Permission is hereby granted, free of charge, to any person obtaining a copy
-//of this software and associated documentation files (the "Software"), to deal
-//in the Software without restriction, including without limitation the rights
-//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-//copies of the Software, and to permit persons to whom the Software is
-//furnished to do so, subject to the following conditions:
-//
-//The above copyright notice and this permission notice shall be included in
-//all copies or substantial portions of the Software.
-//
-//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-//THE SOFTWARE.
 package io.netty.example.http.websocketx.client;
 
-import io.netty.bootstrap.Bootstrap;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
-import io.netty.handler.codec.http.DefaultHttpHeaders;
-import io.netty.handler.codec.http.HttpClientCodec;
-import io.netty.handler.codec.http.HttpHeaders;
-import io.netty.handler.codec.http.HttpObjectAggregator;
-import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
-import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
-import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
-import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
-import io.netty.handler.codec.http.websocketx.WebSocketVersion;
-
 import java.net.URI;
 
-public class WebSocketClient {
-
-    private final URI uri;
-
-    public WebSocketClient(URI uri) {
-        this.uri = uri;
-    }
-
-    public void run() throws Exception {
-        EventLoopGroup group = new NioEventLoopGroup();
-        try {
-            Bootstrap b = new Bootstrap();
-            String protocol = uri.getScheme();
-            if (!"ws".equals(protocol)) {
-                throw new IllegalArgumentException("Unsupported protocol: " + protocol);
-            }
-
-            HttpHeaders customHeaders = new DefaultHttpHeaders();
-            customHeaders.add("MyHeader", "MyValue");
-
-            // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
-            // If you change it to V00, ping is not supported and remember to change
-            // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
-            final WebSocketClientHandler handler =
-                    new WebSocketClientHandler(
-                            WebSocketClientHandshakerFactory.newHandshaker(
-                                    uri, WebSocketVersion.V13, null, false, customHeaders));
-
-            b.group(group)
-             .channel(NioSocketChannel.class)
-             .handler(new ChannelInitializer<SocketChannel>() {
-                 @Override
-                 public void initChannel(SocketChannel ch) throws Exception {
-                     ChannelPipeline pipeline = ch.pipeline();
-                     pipeline.addLast("http-codec", new HttpClientCodec());
-                     pipeline.addLast("aggregator", new HttpObjectAggregator(8192));
-                     pipeline.addLast("ws-handler", handler);
-                 }
-             });
-
-            System.out.println("WebSocket Client connecting");
-            Channel ch = b.connect(uri.getHost(), uri.getPort()).sync().channel();
-            handler.handshakeFuture().sync();
-
-            // Send 10 messages and wait for responses
-            System.out.println("WebSocket Client sending message");
-            for (int i = 0; i < 10; i++) {
-                ch.writeAndFlush(new TextWebSocketFrame("Message #" + i));
-            }
-
-            // Ping
-            System.out.println("WebSocket Client sending ping");
-            ch.writeAndFlush(new PingWebSocketFrame(Unpooled.copiedBuffer(new byte[]{1, 2, 3, 4, 5, 6})));
-
-            // Close
-            System.out.println("WebSocket Client sending close");
-            ch.writeAndFlush(new CloseWebSocketFrame());
-
-            // WebSocketClientHandler will close the connection when the server
-            // responds to the CloseWebSocketFrame.
-            ch.closeFuture().sync();
-        } finally {
-            group.shutdownGracefully();
-        }
+/**
+ * This is an example of a WebSocket client.
+ * <p>
+ * In order to run this example you need a compatible WebSocket server.
+ * Therefore you can either start the WebSocket server from the examples
+ * by running {@link io.netty.example.http.websocketx.server.WebSocketServer}
+ * or connect to an existing WebSocket server such as
+ * <a href="http://www.websocket.org/echo.html">ws://echo.websocket.org</a>.
+ * <p>
+ * The client will attempt to connect to the URI passed to it as the first argument.
+ * You don't have to specify any arguments if you want to connect to the example WebSocket server,
+ * as this is the default.
+ */
+public final class WebSocketClient {
+    private WebSocketClient() {
     }
 
-    public static void main(String[] args) throws Exception {
+    public static void main(String... args) throws Exception {
         URI uri;
         if (args.length > 0) {
             uri = new URI(args[0]);
         } else {
             uri = new URI("ws://localhost:8080/websocket");
         }
-        new WebSocketClient(uri).run();
+
+        new WebSocketClientRunner(uri).run();
     }
 }
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientRunner.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientRunner.java
new file mode 100644
index 00000000000..211a71dcdd3
--- /dev/null
+++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientRunner.java
@@ -0,0 +1,134 @@
+/*
+ * 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.example.http.websocketx.client;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketVersion;
+import io.netty.handler.ssl.SslHandler;
+
+import javax.net.ssl.SSLEngine;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URI;
+
+public final class WebSocketClientRunner {
+
+    private URI uri;
+
+    public WebSocketClientRunner(URI uri) {
+        this.uri = uri;
+    }
+
+    public void run() throws Exception {
+        EventLoopGroup group = new NioEventLoopGroup();
+        try {
+            // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
+            // If you change it to V00, ping is not supported and remember to change
+            // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
+            final WebSocketClientHandler handler =
+                    new WebSocketClientHandler(
+                            WebSocketClientHandshakerFactory.newHandshaker(
+                                    uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()));
+
+            final String protocol = uri.getScheme();
+            int defaultPort;
+            ChannelInitializer<SocketChannel> initializer;
+
+            // Normal WebSocket
+            if ("ws".equals(protocol)) {
+                initializer = new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        ch.pipeline()
+                          .addLast("http-codec", new HttpClientCodec())
+                          .addLast("aggregator", new HttpObjectAggregator(8192))
+                          .addLast("ws-handler", handler);
+                    }
+                };
+
+                defaultPort = 80;
+            // Secure WebSocket
+            } else if ("wss".equals(protocol)) {
+                initializer = new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        SSLEngine engine = WebSocketSslClientContextFactory.getContext().createSSLEngine();
+                        engine.setUseClientMode(true);
+
+                        ch.pipeline()
+                          .addFirst("ssl", new SslHandler(engine))
+                          .addLast("http-codec", new HttpClientCodec())
+                          .addLast("aggregator", new HttpObjectAggregator(8192))
+                          .addLast("ws-handler", handler);
+                    }
+                };
+
+                defaultPort = 443;
+            } else {
+                throw new IllegalArgumentException("Unsupported protocol: " + protocol);
+            }
+
+            Bootstrap b = new Bootstrap();
+            b.group(group)
+             .channel(NioSocketChannel.class)
+             .handler(initializer);
+
+            int port = uri.getPort();
+            // If no port was specified, we'll try the default port: https://tools.ietf.org/html/rfc6455#section-1.7
+            if (uri.getPort() == -1) {
+                port = defaultPort;
+            }
+
+            Channel ch = b.connect(uri.getHost(), port).sync().channel();
+            handler.handshakeFuture().sync();
+
+            BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
+            while (true) {
+                String msg = console.readLine();
+                if (msg == null) {
+                    break;
+                } else if ("bye".equals(msg.toLowerCase())) {
+                    ch.writeAndFlush(new CloseWebSocketFrame());
+                    ch.closeFuture().sync();
+                    break;
+                } else if ("ping".equals(msg.toLowerCase())) {
+                    WebSocketFrame frame = new PingWebSocketFrame(Unpooled.copiedBuffer(new byte[]{8, 1, 8, 1}));
+                    ch.writeAndFlush(frame);
+                } else {
+                    WebSocketFrame frame = new TextWebSocketFrame(msg);
+                    ch.writeAndFlush(frame);
+                }
+            }
+        } finally {
+            group.shutdownGracefully();
+        }
+    }
+}
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClient.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClient.java
new file mode 100644
index 00000000000..7cc1ff6d0be
--- /dev/null
+++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClient.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.example.http.websocketx.client;
+
+import java.net.URI;
+
+/**
+ * This is an example of a secure WebSocket client.
+ * <p>
+ * In order to run this example you need a compatible secure WebSocket server.
+ * Therefore you can either start the secure WebSocket server from the examples
+ * by running {@link io.netty.example.http.websocketx.sslserver.WebSocketSslServer}
+ * or connect to an existing secure WebSocket server such as
+ * <a href="http://www.websocket.org/echo.html">wss://echo.websocket.org</a>.
+ * <p>
+ * The client will attempt to connect to the URI passed to it as the first argument.
+ * You don't have to specify any arguments if you want to connect to the example secure WebSocket server,
+ * as this is the default.
+ */
+public final class WebSocketSslClient {
+    private WebSocketSslClient() {
+    }
+
+    public static void main(String... args) throws Exception {
+        URI uri;
+        if (args.length > 0) {
+            uri = new URI(args[0]);
+        } else {
+            uri = new URI("wss://localhost:8443/websocket");
+        }
+
+        new WebSocketClientRunner(uri).run();
+    }
+}
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientContextFactory.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientContextFactory.java
new file mode 100644
index 00000000000..faa17f704ae
--- /dev/null
+++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientContextFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.example.http.websocketx.client;
+
+import javax.net.ssl.SSLContext;
+
+/**
+ * Creates a bogus {@link javax.net.ssl.SSLContext}.  A client-side context created by this
+ * factory accepts any certificate even if it is invalid.
+ * <p>
+ * You will have to create your context differently in a real world application.
+ * <p>
+ * Modified from {@link io.netty.example.securechat.SecureChatSslContextFactory}
+ */
+public final class WebSocketSslClientContextFactory {
+
+    private static final String PROTOCOL = "TLS";
+    private static final SSLContext CONTEXT;
+
+    static {
+        SSLContext clientContext;
+        try {
+            clientContext = SSLContext.getInstance(PROTOCOL);
+            clientContext.init(null, WebSocketSslClientTrustManagerFactory.getTrustManagers(), null);
+        } catch (Exception e) {
+            throw new Error(
+                    "Failed to initialize the client-side SSLContext", e);
+        }
+
+        CONTEXT = clientContext;
+    }
+
+    public static SSLContext getContext() {
+        return CONTEXT;
+    }
+
+    private WebSocketSslClientContextFactory() {
+        // Unused
+    }
+}
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientTrustManagerFactory.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientTrustManagerFactory.java
new file mode 100644
index 00000000000..2c5f71d0701
--- /dev/null
+++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientTrustManagerFactory.java
@@ -0,0 +1,79 @@
+/*
+ * 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.example.http.websocketx.client;
+
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactorySpi;
+import javax.net.ssl.X509TrustManager;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.X509Certificate;
+
+/**
+ * Bogus {@link javax.net.ssl.TrustManagerFactorySpi} which accepts any certificate
+ * even if it is invalid.
+ * <p>
+ * Copied from {@link io.netty.example.securechat.SecureChatTrustManagerFactory}
+ */
+public class WebSocketSslClientTrustManagerFactory extends TrustManagerFactorySpi {
+
+    private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() {
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) {
+            // Always trust - it is an example.
+            // You should do something in the real world.
+            // You will reach here only if you enabled client certificate auth,
+            // as described in WebSocketSslClientContextFactory.
+            System.err.println(
+                    "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN());
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) {
+            // Always trust - it is an example.
+            // You should do something in the real world.
+            System.err.println(
+                    "UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN());
+        }
+    };
+
+    public static TrustManager[] getTrustManagers() {
+        return new TrustManager[]{DUMMY_TRUST_MANAGER};
+    }
+
+    @Override
+    protected TrustManager[] engineGetTrustManagers() {
+        return getTrustManagers();
+    }
+
+    @Override
+    protected void engineInit(KeyStore keystore) throws KeyStoreException {
+        // Unused
+    }
+
+    @Override
+    protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
+            throws InvalidAlgorithmParameterException {
+        // Unused
+    }
+}
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/package-info.java b/example/src/main/java/io/netty/example/http/websocketx/client/package-info.java
deleted file mode 100644
index f009016a0d1..00000000000
--- a/example/src/main/java/io/netty/example/http/websocketx/client/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2012 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.
- */
-
-/**
- * <p>This is an example web service client.
- * <p>To run this example, you must first start
- * {@link io.netty.example.http.websocketx.server.WebSocketServer} and
- * then {@link io.netty.example.http.websocketx.client.WebSocketClient}.
- */
-package io.netty.example.http.websocketx.client;


Test patch: