From a1a48051f60dd8e8a89e66013ab1ae215d2c98b5 Mon Sep 17 00:00:00 2001 From: Jakob Buchgraber Date: Thu, 27 Feb 2014 03:17:39 +0100 Subject: [PATCH] Added a secure WebSocket client example Merged WebSocketClient and WebSocketSslClient Add private constructors to fix checkstyle errors. More checkstyle madness. made WebSocketClientRunner final --- .../websocketx/client/WebSocketClient.java | 126 +++------------- .../client/WebSocketClientRunner.java | 134 ++++++++++++++++++ .../websocketx/client/WebSocketSslClient.java | 47 ++++++ .../WebSocketSslClientContextFactory.java | 53 +++++++ ...WebSocketSslClientTrustManagerFactory.java | 79 +++++++++++ .../http/websocketx/client/package-info.java | 23 --- 6 files changed, 332 insertions(+), 130 deletions(-) create mode 100644 example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientRunner.java create mode 100644 example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClient.java create mode 100644 example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientContextFactory.java create mode 100644 example/src/main/java/io/netty/example/http/websocketx/client/WebSocketSslClientTrustManagerFactory.java delete mode 100644 example/src/main/java/io/netty/example/http/websocketx/client/package-info.java 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 5e6095167f..6224c45ad1 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; +/** + * This is an example of a WebSocket client. + *

+ * 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 + * ws://echo.websocket.org. + *

+ * 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 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() { - @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(); - } - } - - 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 0000000000..211a71dcdd --- /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 initializer; + + // Normal WebSocket + if ("ws".equals(protocol)) { + initializer = new ChannelInitializer() { + @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() { + @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 0000000000..7cc1ff6d0b --- /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. + *

+ * 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 + * wss://echo.websocket.org. + *

+ * 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 0000000000..faa17f704a --- /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. + *

+ * You will have to create your context differently in a real world application. + *

+ * 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 0000000000..2c5f71d070 --- /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. + *

+ * 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 f009016a0d..0000000000 --- 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. - */ - -/** - *

This is an example web service client. - *

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;