From 5bcbe0f9d9ab83c14584b63ef2ffe18579f89f9e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 22 Aug 2019 16:45:04 -0500 Subject: [PATCH] Adding javax.websocket secure client example Signed-off-by: Joakim Erdfelt --- .../src/test/java/examples/EchoEndpoint.java | 90 +++++++++++++ .../examples/OriginServerConfigurator.java | 44 +++++++ .../SecureWebSocketContainerExample.java | 121 ++++++++++++++++++ .../examples/jetty-websocket-httpclient.xml | 22 ++++ 4 files changed, 277 insertions(+) create mode 100644 jetty-websocket/javax-websocket-client-impl/src/test/java/examples/EchoEndpoint.java create mode 100644 jetty-websocket/javax-websocket-client-impl/src/test/java/examples/OriginServerConfigurator.java create mode 100644 jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureWebSocketContainerExample.java create mode 100644 jetty-websocket/javax-websocket-client-impl/src/test/resources/examples/jetty-websocket-httpclient.xml diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/EchoEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/EchoEndpoint.java new file mode 100644 index 00000000000..cea500b23bf --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/EchoEndpoint.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package examples; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +/** + * Basic Echo Client Endpoint + */ +public class EchoEndpoint extends Endpoint implements MessageHandler.Whole +{ + private final CountDownLatch closeLatch = new CountDownLatch(1); + private Session session; + + public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException + { + return this.closeLatch.await(duration, unit); + } + + @Override + public void onClose(Session session, CloseReason closeReason) + { + System.out.printf("Connection closed: Session.id=%s - %s%n", session.getId(), closeReason); + this.session = null; + this.closeLatch.countDown(); // trigger latch + } + + @Override + public void onOpen(Session session, EndpointConfig config) + { + System.out.printf("Got open: Session.id=%s%n", session.getId()); + this.session = session; + this.session.addMessageHandler(this); + try + { + session.getBasicRemote().sendText("Hello"); + session.getBasicRemote().sendText("Thanks for the conversation."); + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + + @Override + public void onMessage(String msg) + { + System.out.printf("Got msg: \"%s\"%n", msg); + if (msg.contains("Thanks")) + { + try + { + session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "I'm done")); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + @Override + public void onError(Session session, Throwable cause) + { + cause.printStackTrace(); + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/OriginServerConfigurator.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/OriginServerConfigurator.java new file mode 100644 index 00000000000..cf08984074f --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/OriginServerConfigurator.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package examples; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.websocket.ClientEndpointConfig; + +/** + * Provide a means to set the `Origin` header for outgoing WebSocket upgrade requests + */ +public class OriginServerConfigurator extends ClientEndpointConfig.Configurator +{ + private final String originServer; + + public OriginServerConfigurator(String originServer) + { + this.originServer = originServer; + } + + @Override + public void beforeRequest(Map> headers) + { + headers.put("Origin", Collections.singletonList(originServer)); + super.beforeRequest(headers); + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureWebSocketContainerExample.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureWebSocketContainerExample.java new file mode 100644 index 00000000000..c70fc4ffe0b --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureWebSocketContainerExample.java @@ -0,0 +1,121 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package examples; + +import java.io.FileNotFoundException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.concurrent.TimeUnit; +import javax.websocket.ClientEndpointConfig; +import javax.websocket.ContainerProvider; +import javax.websocket.WebSocketContainer; + +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; + +public class SecureWebSocketContainerExample +{ + public static void main(String[] args) + { + String destUri = "wss://echo.websocket.org"; + if (args.length > 0) + { + destUri = args[0]; + } + + WebSocketContainer client = null; + try + { + client = getConfiguredWebSocketContainer(); + URI echoUri = new URI(destUri); + + ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create() + .configurator(new OriginServerConfigurator("https://websocket.org")) + .build(); + EchoEndpoint echoEndpoint = new EchoEndpoint(); + client.connectToServer(echoEndpoint, clientEndpointConfig, echoUri); + System.out.printf("Connecting to : %s%n", echoUri); + + // wait for closed socket connection. + echoEndpoint.awaitClose(5, TimeUnit.SECONDS); + } + catch (Throwable t) + { + t.printStackTrace(); + } + finally + { + /* Since javax.websocket clients have no defined LifeCycle we + * want to either close/stop the client, or exit the JVM + * via a System.exit(), otherwise the threads this client keeps + * open will prevent the JVM from terminating naturally. + */ + LifeCycle.stop(client); + } + } + + /** + * Since javax.websocket does not have an API for configuring SSL, each implementation + * of javax.websocket has to come up with their own SSL configuration mechanism. + *

+ * When the call to {@link javax.websocket.ContainerProvider}.{@link ContainerProvider#getWebSocketContainer()} + * occurs, that needs to have a started and available WebSocket Client. + * Jetty's {@code WebSocketClient} must have a Jetty {@code HttpClient} started as well. + * If you want SSL, then that configuration has to be passed into the Jetty {@code HttpClient} at initialization. + *

+ *

+ * How Jetty makes this available, is via the {@code jetty-websocket-httpclient.xml} classloader resource + * along with the jetty-xml artifact. + *

+ *

+ * This method will look for the file in the classloader resources, and then + * sets up a {@link URLClassLoader} to make that {@code jetty-websocket-httpclient.xml} available + * for this specific example. + * If we had put the `jetty-websocket-httpclient.xml` in the root of a JAR file loaded by this + * project then you can skip all of the classloader trickery this method performs. + *

+ * + * @return the client WebSocketContainer + * @see javax.websocket issue #210 + */ + public static WebSocketContainer getConfiguredWebSocketContainer() throws Exception + { + URL jettyHttpClientConfigUrl = Thread.currentThread().getContextClassLoader() + .getResource("examples/jetty-websocket-httpclient.xml"); + + if (jettyHttpClientConfigUrl == null) + { + throw new FileNotFoundException("Unable to find Jetty HttpClient configuration XML"); + } + + URI jettyConfigDirUri = jettyHttpClientConfigUrl.toURI().resolve("./"); + + ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader(); + URL[] urls = new URL[]{ + jettyConfigDirUri.toURL() + }; + URLClassLoader classLoader = new URLClassLoader(urls, parentClassLoader); + + try (ThreadClassLoaderScope ignore = new ThreadClassLoaderScope(classLoader)) + { + return ContainerProvider.getWebSocketContainer(); + } + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/resources/examples/jetty-websocket-httpclient.xml b/jetty-websocket/javax-websocket-client-impl/src/test/resources/examples/jetty-websocket-httpclient.xml new file mode 100644 index 00000000000..672007a92cd --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/resources/examples/jetty-websocket-httpclient.xml @@ -0,0 +1,22 @@ + + + + + + false + + + + TLS/1.3 + + + + + + + + + + + 5000 +