diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java index 81ebff5ba9e3..f9341976e55c 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java @@ -32,17 +32,26 @@ @Deprecated public abstract class ExtensionFactory implements Iterable> { - private ServiceLoader extensionLoader = ServiceLoader.load(Extension.class); - private Map> availableExtensions; + private final Map> availableExtensions; public ExtensionFactory() { availableExtensions = new HashMap<>(); - for (Extension ext : extensionLoader) + Iterator iterator = ServiceLoader.load(Extension.class).iterator(); + while (true) { - if (ext != null) + try { - availableExtensions.put(ext.getName(), ext.getClass()); + if (!iterator.hasNext()) + break; + + Extension ext = iterator.next(); + if (ext != null) + availableExtensions.put(ext.getName(), ext.getClass()); + } + catch (Throwable ignored) + { + // Ignored. } } } diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml index f76dfccfc07c..031f3a509b96 100644 --- a/jetty-websocket/websocket-client/pom.xml +++ b/jetty-websocket/websocket-client/pom.xml @@ -24,6 +24,7 @@ org.eclipse.jetty jetty-xml ${project.version} + true org.eclipse.jetty diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/HttpClientProvider.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/HttpClientProvider.java index e2c89dab64df..bd91f697e66e 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/HttpClientProvider.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/HttpClientProvider.java @@ -18,33 +18,16 @@ package org.eclipse.jetty.websocket.client; -import java.lang.reflect.Method; - import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; public final class HttpClientProvider { public static HttpClient get(WebSocketContainerScope scope) { - try - { - if (Class.forName("org.eclipse.jetty.xml.XmlConfiguration") != null) - { - Class xmlClazz = Class.forName("org.eclipse.jetty.websocket.client.XmlBasedHttpClientProvider"); - Method getMethod = xmlClazz.getMethod("get", WebSocketContainerScope.class); - Object ret = getMethod.invoke(null, scope); - if ((ret != null) && (ret instanceof HttpClient)) - { - return (HttpClient)ret; - } - } - } - catch (Throwable ignore) - { - Log.getLogger(HttpClientProvider.class).ignore(ignore); - } + HttpClient httpClient = XmlBasedHttpClientProvider.get(scope); + if (httpClient != null) + return httpClient; return DefaultHttpClientProvider.newHttpClient(scope); } diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/XmlBasedHttpClientProvider.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/XmlBasedHttpClientProvider.java index e4a7a09199f5..b93f0f3bb89c 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/XmlBasedHttpClientProvider.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/XmlBasedHttpClientProvider.java @@ -22,27 +22,46 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.xml.XmlConfiguration; class XmlBasedHttpClientProvider { + public static final Logger LOG = Log.getLogger(XmlBasedHttpClientProvider.class); + public static HttpClient get(@SuppressWarnings("unused") WebSocketContainerScope scope) { - URL resource = Thread.currentThread().getContextClassLoader().getResource("jetty-websocket-httpclient.xml"); + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader == null) + return null; + + URL resource = contextClassLoader.getResource("jetty-websocket-httpclient.xml"); if (resource == null) - { return null; + + try + { + Thread.currentThread().setContextClassLoader(HttpClient.class.getClassLoader()); + return newHttpClient(resource); + } + finally + { + Thread.currentThread().setContextClassLoader(contextClassLoader); } + } + private static HttpClient newHttpClient(URL resource) + { try { - XmlConfiguration configuration = new XmlConfiguration(resource); + XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(resource)); return (HttpClient)configuration.configure(); } catch (Throwable t) { - Log.getLogger(XmlBasedHttpClientProvider.class).warn("Unable to load: " + resource, t); + LOG.warn("Failure to load HttpClient from XML {}", resource, t); } return null; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java index cf275487a64d..70205569d369 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java @@ -19,11 +19,6 @@ package org.eclipse.jetty.websocket.common.extensions; import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.Set; import java.util.zip.Deflater; import org.eclipse.jetty.util.StringUtil; @@ -42,10 +37,8 @@ public class WebSocketExtensionFactory extends ExtensionFactory implements LifeCycle, Dumpable { - private ContainerLifeCycle containerLifeCycle; - private WebSocketContainerScope container; - private ServiceLoader extensionLoader = ServiceLoader.load(Extension.class); - private Map> availableExtensions; + private final ContainerLifeCycle containerLifeCycle; + private final WebSocketContainerScope container; private final InflaterPool inflaterPool = new InflaterPool(CompressionPool.INFINITE_CAPACITY, true); private final DeflaterPool deflaterPool = new DeflaterPool(CompressionPool.INFINITE_CAPACITY, Deflater.DEFAULT_COMPRESSION, true); @@ -59,42 +52,12 @@ public String toString() return String.format("%s@%x{%s}", WebSocketExtensionFactory.class.getSimpleName(), hashCode(), containerLifeCycle.getState()); } }; - availableExtensions = new HashMap<>(); - for (Extension ext : extensionLoader) - { - if (ext != null) - availableExtensions.put(ext.getName(), ext.getClass()); - } this.container = container; containerLifeCycle.addBean(inflaterPool); containerLifeCycle.addBean(deflaterPool); } - @Override - public Map> getAvailableExtensions() - { - return availableExtensions; - } - - @Override - public Class getExtension(String name) - { - return availableExtensions.get(name); - } - - @Override - public Set getExtensionNames() - { - return availableExtensions.keySet(); - } - - @Override - public boolean isAvailable(String name) - { - return availableExtensions.containsKey(name); - } - @Override public Extension newInstance(ExtensionConfig config) { @@ -139,24 +102,6 @@ public Extension newInstance(ExtensionConfig config) } } - @Override - public void register(String name, Class extension) - { - availableExtensions.put(name, extension); - } - - @Override - public void unregister(String name) - { - availableExtensions.remove(name); - } - - @Override - public Iterator> iterator() - { - return availableExtensions.values().iterator(); - } - /* --- All of the below ugliness due to not being able to break API compatibility with ExtensionFactory --- */ @Override diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index eaf9a3960e3f..76326d54c86e 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -117,6 +117,18 @@ ${project.version} test + + org.eclipse.jetty.websocket + websocket-api + ${project.version} + test + + + org.eclipse.jetty.websocket + websocket-client + ${project.version} + test + org.eclipse.jetty.tests test-felix-webapp diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractDistributionTest.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractDistributionTest.java index f4428f0f5c2f..bb8d2b3e8410 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractDistributionTest.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractDistributionTest.java @@ -21,6 +21,7 @@ import java.util.function.Supplier; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.AfterEach; public class AbstractDistributionTest @@ -29,7 +30,15 @@ public class AbstractDistributionTest protected void startHttpClient() throws Exception { - startHttpClient(HttpClient::new); + startHttpClient(false); + } + + protected void startHttpClient(boolean secure) throws Exception + { + if (secure) + startHttpClient(() -> new HttpClient(new SslContextFactory.Client(true))); + else + startHttpClient(HttpClient::new); } protected void startHttpClient(Supplier supplier) throws Exception diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index f1702270823b..d89185bda5db 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.tests.distribution; import java.io.File; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -39,6 +40,8 @@ import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -304,4 +307,103 @@ public void testLog4j2ModuleWithSimpleWebAppWithJSP() throws Exception IO.delete(jettyBase.toFile()); } } + + @ParameterizedTest + @ValueSource(strings = {"http", "https"}) + public void testWebsocketClientInWebappProvidedByServer(String scheme) throws Exception + { + Path jettyBase = Files.createTempDirectory("jetty_base"); + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + String[] args1 = { + "--create-startd", + "--approve-all-licenses", + "--add-to-start=resources,server,webapp,deploy,jsp,jmx,servlet,servlets,websocket," + scheme + }; + try (DistributionTester.Run run1 = distribution.start(args1)) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + File webApp = distribution.resolveArtifact("org.eclipse.jetty.tests:test-websocket-client-provided-webapp:war:" + jettyVersion); + distribution.installWarFile(webApp, "test"); + + int port = distribution.freePort(); + String[] args2 = { + "jetty.http.port=" + port, + "jetty.ssl.port=" + port, + // "jetty.server.dumpAfterStart=true", + }; + + try (DistributionTester.Run run2 = distribution.start(args2)) + { + assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + + // We should get the correct configuration from the jetty-websocket-httpclient.xml file. + startHttpClient(scheme.equals("https")); + URI serverUri = URI.create(scheme + "://localhost:" + port + "/test"); + ContentResponse response = client.GET(serverUri); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String content = response.getContentAsString(); + assertThat(content, containsString("WebSocketEcho: success")); + assertThat(content, containsString("ConnectTimeout: 4999")); + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"http", "https"}) + public void testWebsocketClientInWebapp(String scheme) throws Exception + { + Path jettyBase = Files.createTempDirectory("jetty_base"); + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + String[] args1 = { + "--create-startd", + "--approve-all-licenses", + "--add-to-start=resources,server,webapp,deploy,jsp,jmx,servlet,servlets,websocket," + scheme + }; + try (DistributionTester.Run run1 = distribution.start(args1)) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + File webApp = distribution.resolveArtifact("org.eclipse.jetty.tests:test-websocket-client-webapp:war:" + jettyVersion); + distribution.installWarFile(webApp, "test"); + + int port = distribution.freePort(); + String[] args2 = { + "jetty.http.port=" + port, + "jetty.ssl.port=" + port, + // We must hide the websocket classes from the webapp if we are to include websocket client jars in WEB-INF/lib. + "jetty.webapp.addServerClasses+=,+org.eclipse.jetty.websocket.", + "jetty.webapp.addSystemClasses+=,-org.eclipse.jetty.websocket.", + // "jetty.server.dumpAfterStart=true", + }; + + try (DistributionTester.Run run2 = distribution.start(args2)) + { + assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + + // We should get the correct configuration from the jetty-websocket-httpclient.xml file. + startHttpClient(scheme.equals("https")); + URI serverUri = URI.create(scheme + "://localhost:" + port + "/test"); + ContentResponse response = client.GET(serverUri); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String content = response.getContentAsString(); + assertThat(content, containsString("WebSocketEcho: success")); + assertThat(content, containsString("ConnectTimeout: 4999")); + } + } + } } diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index ec73057e341e..69c4014557ff 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -43,5 +43,7 @@ test-cdi-common-webapp test-weld-cdi-webapp test-owb-cdi-webapp + test-websocket-client-webapp + test-websocket-client-provided-webapp diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml b/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml new file mode 100644 index 000000000000..9f57388e81ed --- /dev/null +++ b/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml @@ -0,0 +1,33 @@ + + + + org.eclipse.jetty.tests + test-webapps-parent + 9.4.34-SNAPSHOT + + + 4.0.0 + test-websocket-client-provided-webapp + war + + Test :: Jetty Websocket Simple Webapp with WebSocketClient + + + + javax.servlet + javax.servlet-api + provided + + + javax.websocket + javax.websocket-api + provided + + + org.eclipse.jetty.websocket + websocket-client + ${project.version} + provided + + + diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java new file mode 100644 index 000000000000..2120c5d7cca3 --- /dev/null +++ b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java @@ -0,0 +1,32 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// 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 org.eclipse.jetty.tests.webapp.websocket; + +import javax.websocket.OnMessage; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint(value = "/echo") +public class EchoEndpoint +{ + @OnMessage + public String echo(String message) + { + return message; + } +} diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/WebSocketClientServlet.java b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/WebSocketClientServlet.java new file mode 100644 index 000000000000..8dbbc957b843 --- /dev/null +++ b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/WebSocketClientServlet.java @@ -0,0 +1,133 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// 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 org.eclipse.jetty.tests.webapp.websocket; + +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.WebSocketClient; + +@WebServlet("/") +public class WebSocketClientServlet extends HttpServlet +{ + private WebSocketClient client; + + @Override + public void init() throws ServletException + { + // Cannot instantiate an HttpClient here because it's a server class, and therefore must rely on jetty-websocket-httpclient.xml + client = new WebSocketClient(); + + try + { + client.start(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + @Override + public void destroy() + { + try + { + client.stop(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + { + try + { + resp.setContentType("text/html"); + + // Send and receive a websocket echo on the same server. + ClientSocket clientSocket = new ClientSocket(); + URI wsUri = WSURI.toWebsocket(req.getRequestURL()).resolve("echo"); + client.connect(clientSocket, wsUri).get(5, TimeUnit.SECONDS); + clientSocket.session.getRemote().sendString("test message"); + String response = clientSocket.textMessages.poll(5, TimeUnit.SECONDS); + clientSocket.session.close(); + clientSocket.closeLatch.await(5, TimeUnit.SECONDS); + + PrintWriter writer = resp.getWriter(); + writer.println("WebSocketEcho: " + ("test message".equals(response) ? "success" : "failure")); + writer.println("WebSocketEcho: success"); + + // We need to test HttpClient timeout with reflection because it is a server class not exposed to the webapp. + Object httpClient = client.getHttpClient(); + Method getConnectTimeout = httpClient.getClass().getMethod("getConnectTimeout"); + writer.println("ConnectTimeout: " + getConnectTimeout.invoke(httpClient)); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @WebSocket + public static class ClientSocket + { + public Session session; + public CountDownLatch openLatch = new CountDownLatch(1); + public CountDownLatch closeLatch = new CountDownLatch(1); + public ArrayBlockingQueue textMessages = new ArrayBlockingQueue<>(10); + + @OnWebSocketConnect + public void onOpen(Session session) + { + this.session = session; + openLatch.countDown(); + } + + @OnWebSocketMessage + public void onMessage(String message) + { + textMessages.add(message); + } + + @OnWebSocketClose + public void onClose(int statusCode, String reason) + { + closeLatch.countDown(); + } + } +} diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/src/main/resources/jetty-websocket-httpclient.xml b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/resources/jetty-websocket-httpclient.xml new file mode 100644 index 000000000000..49b65a057464 --- /dev/null +++ b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/resources/jetty-websocket-httpclient.xml @@ -0,0 +1,10 @@ + + + + + + true + + + 4999 + diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..99907fad52a3 --- /dev/null +++ b/tests/test-webapps/test-websocket-client-provided-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,8 @@ + + + diff --git a/tests/test-webapps/test-websocket-client-webapp/pom.xml b/tests/test-webapps/test-websocket-client-webapp/pom.xml new file mode 100644 index 000000000000..d4e41a770eca --- /dev/null +++ b/tests/test-webapps/test-websocket-client-webapp/pom.xml @@ -0,0 +1,32 @@ + + + + org.eclipse.jetty.tests + test-webapps-parent + 9.4.34-SNAPSHOT + + + 4.0.0 + test-websocket-client-webapp + war + + Test :: Jetty Websocket Simple Webapp with WebSocketClient + + + + javax.servlet + javax.servlet-api + provided + + + javax.websocket + javax.websocket-api + provided + + + org.eclipse.jetty.websocket + websocket-client + ${project.version} + + + diff --git a/tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java b/tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java new file mode 100644 index 000000000000..2120c5d7cca3 --- /dev/null +++ b/tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/EchoEndpoint.java @@ -0,0 +1,32 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// 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 org.eclipse.jetty.tests.webapp.websocket; + +import javax.websocket.OnMessage; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint(value = "/echo") +public class EchoEndpoint +{ + @OnMessage + public String echo(String message) + { + return message; + } +} diff --git a/tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/WebSocketClientServlet.java b/tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/WebSocketClientServlet.java new file mode 100644 index 000000000000..36401a42e88c --- /dev/null +++ b/tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/tests/webapp/websocket/WebSocketClientServlet.java @@ -0,0 +1,133 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// 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 org.eclipse.jetty.tests.webapp.websocket; + +import java.io.PrintWriter; +import java.net.URI; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.WebSocketClient; + +@WebServlet("/") +public class WebSocketClientServlet extends HttpServlet +{ + private WebSocketClient client; + + @Override + public void init() throws ServletException + { + // We can't use the jetty-websocket-httpclient.xml if the websocket client jars are in WEB-INF/lib. + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true); + HttpClient httpClient = new HttpClient(sslContextFactory); + httpClient.setConnectTimeout(4999); + this.client = new WebSocketClient(httpClient); + + try + { + this.client.start(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + @Override + public void destroy() + { + try + { + client.stop(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + { + try + { + resp.setContentType("text/html"); + + // Send and receive a websocket echo on the same server. + ClientSocket clientSocket = new ClientSocket(); + URI wsUri = WSURI.toWebsocket(req.getRequestURL()).resolve("echo"); + client.connect(clientSocket, wsUri).get(5, TimeUnit.SECONDS); + clientSocket.session.getRemote().sendString("test message"); + String response = clientSocket.textMessages.poll(5, TimeUnit.SECONDS); + clientSocket.session.close(); + clientSocket.closeLatch.await(5, TimeUnit.SECONDS); + + PrintWriter writer = resp.getWriter(); + writer.println("WebSocketEcho: " + ("test message".equals(response) ? "success" : "failure")); + writer.println("WebSocketEcho: success"); + writer.println("ConnectTimeout: " + client.getHttpClient().getConnectTimeout()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @WebSocket + public static class ClientSocket + { + public Session session; + public CountDownLatch openLatch = new CountDownLatch(1); + public CountDownLatch closeLatch = new CountDownLatch(1); + public ArrayBlockingQueue textMessages = new ArrayBlockingQueue<>(10); + + @OnWebSocketConnect + public void onOpen(Session session) + { + this.session = session; + openLatch.countDown(); + } + + @OnWebSocketMessage + public void onMessage(String message) + { + textMessages.add(message); + } + + @OnWebSocketClose + public void onClose(int statusCode, String reason) + { + closeLatch.countDown(); + } + } +} diff --git a/tests/test-webapps/test-websocket-client-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-websocket-client-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..99907fad52a3 --- /dev/null +++ b/tests/test-webapps/test-websocket-client-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,8 @@ + + +