From 3b31ecc2b1dff2f3b6df9e8bc425239d662dffa2 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 4 May 2017 13:37:29 -0700 Subject: [PATCH] Issue #207 - more test induced fixes --- .../jsr356/server/IdleTimeoutTest.java | 127 ----- .../jsr356/server/SessionTrackingTest.java | 183 ------- .../websocket/common/WebSocketSession.java | 280 ++++++----- .../function/CommonEndpointFunctions.java | 11 + .../common/function/EndpointFunctions.java | 3 + .../io/AbstractWebSocketConnection.java | 6 +- .../jetty/websocket/tests/LocalFuzzer.java | 24 +- .../jetty/websocket/tests/LocalServer.java | 15 +- .../jetty/websocket/tests/Sha1Sum.java | 111 +++++ .../websocket/tests/UnitExtensionStack.java | 105 +++++ .../jetty/websocket/tests/WSServer.java | 108 +---- .../tests/server/DecoratorsLegacyTest.java | 204 -------- .../tests/server/DecoratorsTest.java | 163 ++++--- .../websocket/tests/server/FirefoxTest.java | 99 +--- .../tests/server/FragmentExtensionTest.java | 97 ++-- .../tests/server/IdentityExtensionTest.java | 104 ++-- .../tests/server/IdleTimeoutTest.java | 85 ++-- .../tests/server/RequestHeadersTest.java | 105 ----- .../tests/server/SuspendResumeTest.java | 89 ++-- .../tests/server/WebSocketCloseTest.java | 445 ++++++++---------- .../server/WebSocketServerSessionTest.java | 112 ++--- .../WebSocketUpgradeFilterEmbeddedTest.java | 134 ++++++ .../server/WebSocketUpgradeFilterTest.java | 387 +++------------ .../WebSocketUpgradeFilterWebappTest.java | 111 +++++ .../jsr356/AnnotatedServerEndpointTest.java | 2 +- .../jsr356/IdleTimeoutContextListener.java | 56 +++ .../tests/server/jsr356/IdleTimeoutTest.java | 106 +++++ .../server/jsr356/SessionTrackingTest.java | 173 +++++++ .../tests/server/jsr356}/StreamTest.java | 7 +- .../sockets/IdleTimeoutOnOpenEndpoint.java | 44 ++ .../sockets/IdleTimeoutOnOpenSocket.java | 40 ++ .../resources/idle-timeout-config-web.xml | 12 + .../test/resources/jetty-logging.properties | 6 +- 33 files changed, 1697 insertions(+), 1857 deletions(-) delete mode 100644 jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java delete mode 100644 jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java create mode 100644 jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/Sha1Sum.java create mode 100644 jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/UnitExtensionStack.java delete mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsLegacyTest.java delete mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/RequestHeadersTest.java create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterEmbeddedTest.java create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterWebappTest.java create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutContextListener.java create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutTest.java create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/SessionTrackingTest.java rename jetty-websocket/{javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server => websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356}/StreamTest.java (97%) create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenEndpoint.java create mode 100644 jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenSocket.java create mode 100644 jetty-websocket/websocket-tests/src/test/resources/idle-timeout-config-web.xml diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java deleted file mode 100644 index 546aaa3e4a2..00000000000 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java +++ /dev/null @@ -1,127 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.jsr356.server; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - -import java.net.URI; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.toolchain.test.TestingDir; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule; -import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.IdleTimeoutContextListener; -import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutEndpoint; -import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutSocket; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; - -public class IdleTimeoutTest -{ - private static final Logger LOG = Log.getLogger(IdleTimeoutTest.class); - - @Rule - public TestingDir testdir = new TestingDir(); - - @Rule - public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test"); - - private static WSServer server; - - @BeforeClass - public static void setupServer() throws Exception - { - server = new WSServer(MavenTestingUtils.getTargetTestingPath(IdleTimeoutTest.class.getName()),"app"); - server.copyWebInf("idle-timeout-config-web.xml"); - // the endpoint (extends javax.websocket.Endpoint) - server.copyClass(OnOpenIdleTimeoutEndpoint.class); - // the configuration that adds the endpoint - server.copyClass(IdleTimeoutContextListener.class); - // the annotated socket - server.copyClass(OnOpenIdleTimeoutSocket.class); - - server.start(); - - WebAppContext webapp = server.createWebAppContext(); - server.deployWebapp(webapp); - // wsb.dump(); - } - - @AfterClass - public static void stopServer() - { - server.stop(); - } - - private void assertConnectionTimeout(URI uri) throws Exception - { - WebSocketClient client = new WebSocketClient(bufferPool); - try - { - client.start(); - JettyEchoSocket clientSocket = new JettyEchoSocket(); - - Future clientConnectFuture = client.connect(clientSocket,uri); - // wait for connect - clientConnectFuture.get(1,TimeUnit.SECONDS); - // wait 1 second - TimeUnit.SECONDS.sleep(1); - - // Try to write - clientSocket.sendMessage("You shouldn't be there"); - - // See if remote sent anything (it shouldn't have) - String incomingMessage = clientSocket.messageQueue.poll(1, TimeUnit.SECONDS); - assertThat("Should not have received messages echoed back",incomingMessage,nullValue()); - - // wait for local close - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.SHUTDOWN, containsString("Idle Timeout")); - } - finally - { - client.stop(); - } - } - - @Test - public void testAnnotated() throws Exception - { - URI uri = server.getServerBaseURI(); - assertConnectionTimeout(uri.resolve("idle-onopen-socket")); - } - - @Test - public void testEndpoint() throws Exception - { - URI uri = server.getServerBaseURI(); - assertConnectionTimeout(uri.resolve("idle-onopen-endpoint")); - } -} diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java deleted file mode 100644 index 1957795477f..00000000000 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java +++ /dev/null @@ -1,183 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.jsr356.server; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.net.URI; -import java.util.Collection; -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.OnMessage; -import javax.websocket.Session; -import javax.websocket.server.ServerEndpoint; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.jsr356.ClientContainer; -import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; -import org.eclipse.jetty.websocket.server.WebSocketServerFactory; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -public class SessionTrackingTest -{ - public static class ClientSocket extends Endpoint - { - public Session session; - public CountDownLatch openLatch = new CountDownLatch(1); - public CountDownLatch closeLatch = new CountDownLatch(1); - - @Override - public void onOpen(Session session, EndpointConfig config) - { - this.session = session; - openLatch.countDown(); - } - - @Override - public void onClose(Session session, CloseReason closeReason) - { - closeLatch.countDown(); - } - - public void waitForOpen(long timeout, TimeUnit unit) throws InterruptedException - { - assertThat("ClientSocket opened",openLatch.await(timeout,unit),is(true)); - } - - public void waitForClose(long timeout, TimeUnit unit) throws InterruptedException - { - assertThat("ClientSocket opened",closeLatch.await(timeout,unit),is(true)); - } - } - - @ServerEndpoint("/test") - public static class EchoSocket - { - @OnMessage - public String echo(String msg) - { - return msg; - } - } - - private static Server server; - private static WebSocketServerFactory wsServerFactory; - private static URI serverURI; - - @BeforeClass - public static void startServer() throws Exception - { - Server server = new Server(); - ServerConnector serverConnector = new ServerConnector(server); - serverConnector.setPort(0); - server.addConnector(serverConnector); - ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); - servletContextHandler.setContextPath("/"); - server.setHandler(servletContextHandler); - - ServerContainer serverContainer = WebSocketServerContainerInitializer.configureContext(servletContextHandler); - serverContainer.addEndpoint(EchoSocket.class); - - wsServerFactory = serverContainer.getWebSocketServerFactory(); - - server.start(); - - String host = serverConnector.getHost(); - if (StringUtil.isBlank(host)) - { - host = "localhost"; - } - serverURI = new URI("ws://" + host + ":" + serverConnector.getLocalPort()); - } - - @AfterClass - public static void stopServer() throws Exception - { - if (server == null) - { - return; - } - - server.stop(); - } - - @Test - public void testAddRemoveSessions() throws Exception - { - // Create Client - ClientContainer clientContainer = new ClientContainer(); - try - { - clientContainer.start(); - - // Establish connections - ClientSocket cli1 = new ClientSocket(); - clientContainer.connectToServer(cli1,serverURI.resolve("/test")); - cli1.waitForOpen(1,TimeUnit.SECONDS); - - // Assert open connections - assertServerOpenConnectionCount(1); - - // Establish new connection - ClientSocket cli2 = new ClientSocket(); - clientContainer.connectToServer(cli2,serverURI.resolve("/test")); - cli2.waitForOpen(1,TimeUnit.SECONDS); - - // Assert open connections - assertServerOpenConnectionCount(2); - - // Establish close both connections - cli1.session.close(); - cli2.session.close(); - - cli1.waitForClose(1,TimeUnit.SECONDS); - cli2.waitForClose(1,TimeUnit.SECONDS); - - // Assert open connections - assertServerOpenConnectionCount(0); - } - finally - { - clientContainer.stop(); - } - } - - private void assertServerOpenConnectionCount(int expectedCount) - { - Collection sessions = wsServerFactory.getBeans(javax.websocket.Session.class); - int openCount = 0; - for (javax.websocket.Session session : sessions) - { - assertThat("Session.isopen: " + session,session.isOpen(),is(true)); - openCount++; - } - assertThat("Open Session Count",openCount,is(expectedCount)); - } -} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index 95e838af366..421775287ff 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -86,27 +86,29 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem private final Executor executor; private final AtomicConnectionState connectionState = new AtomicConnectionState(); private final AtomicBoolean closeSent = new AtomicBoolean(); - + // The websocket endpoint object itself private final Object endpoint; // Callbacks - private FrameCallback onDisconnectCallback = new CompletionCallback() { + private FrameCallback onDisconnectCallback = new CompletionCallback() + { @Override public void complete() { if (connectionState.onClosed()) { - LOG.debug("ConnectionState: Transition to CLOSED"); + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: Transition to CLOSED"); connection.disconnect(); } } }; - + // Endpoint Functions and MessageSinks protected EndpointFunctions endpointFunctions; private MessageSink activeMessageSink; - + private ClassLoader classLoader; private ExtensionFactory extensionFactory; private BatchMode batchmode = BatchMode.AUTO; @@ -124,9 +126,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { Objects.requireNonNull(containerScope, "Container Scope cannot be null"); Objects.requireNonNull(requestURI, "Request URI cannot be null"); - + LOG = Log.getLogger(WebSocketSession.class.getName() + "." + connection.getPolicy().getBehavior().name()); - + this.classLoader = Thread.currentThread().getContextClassLoader(); this.containerScope = containerScope; this.requestURI = requestURI; @@ -135,7 +137,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem this.executor = connection.getExecutor(); this.outgoingHandler = connection; this.policy = connection.getPolicy(); - + addBean(this.connection); } @@ -155,13 +157,13 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem /* This is assumed to always be a NORMAL closure, no reason phrase */ close(StatusCode.NORMAL, null); } - + @Override public void close(CloseStatus closeStatus) { - close(closeStatus.getCode(),closeStatus.getPhrase()); + close(closeStatus.getCode(), closeStatus.getPhrase()); } - + @Override public void close(int statusCode, String reason) { @@ -177,19 +179,21 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { connectionState.onClosing(); // move to CLOSING state (always) - if(closeSent.compareAndSet(false,true)) + if (closeSent.compareAndSet(false, true)) { - LOG.debug("Sending Close Frame"); + if (LOG.isDebugEnabled()) + LOG.debug("Sending Close Frame"); CloseFrame closeFrame = closeInfo.asFrame(); outgoingHandler.outgoingFrame(closeFrame, callback, BatchMode.OFF); } else { - LOG.debug("Close Frame Previously Sent: ignoring: {} [{}]", closeInfo, callback); + if (LOG.isDebugEnabled()) + LOG.debug("Close Frame Previously Sent: ignoring: {} [{}]", closeInfo, callback); callback.succeed(); } } - + /** * Harsh disconnect */ @@ -198,31 +202,31 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { connection.disconnect(); } - + public void dispatch(Runnable runnable) { executor.execute(runnable); } - + @Override protected void doStart() throws Exception { if (LOG.isDebugEnabled()) LOG.debug("starting - {}", this); - + Iterator iter = ServiceLoader.load(RemoteEndpointFactory.class).iterator(); if (iter.hasNext()) remoteEndpointFactory = iter.next(); - + if (remoteEndpointFactory == null) remoteEndpointFactory = this; - + if (LOG.isDebugEnabled()) LOG.debug("Using RemoteEndpointFactory: {}", remoteEndpointFactory); - + this.endpointFunctions = newEndpointFunctions(this.endpoint); addManaged(this.endpointFunctions); - + super.doStart(); connection.setMaxIdleTimeout(this.policy.getIdleTimeout()); @@ -232,27 +236,27 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { fastFail = pendingError.get(); } - if(fastFail != null) + if (fastFail != null) onError(fastFail); } - + @Override protected void doStop() throws Exception { if (LOG.isDebugEnabled()) LOG.debug("stopping - {}", this); - + try { - close(StatusCode.SHUTDOWN,"Shutdown"); + close(StatusCode.SHUTDOWN, "Shutdown"); } - catch (Throwable t) + catch (Throwable ignore) { - LOG.debug("During Connection Shutdown",t); + LOG.ignore(ignore); } super.doStop(); } - + @Override public void dump(Appendable out, String indent) throws IOException { @@ -268,7 +272,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem out.append(outgoingHandler.toString()).append(System.lineSeparator()); } } - + @Override public boolean equals(Object obj) { @@ -298,17 +302,17 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem } return true; } - + public ByteBufferPool getBufferPool() { return this.connection.getBufferPool(); } - + public ClassLoader getClassLoader() { return this.getClass().getClassLoader(); } - + public LogicalConnection getConnection() { return connection; @@ -324,17 +328,17 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { return this.containerScope; } - + public Executor getExecutor() { return executor; } - + public ExtensionFactory getExtensionFactory() { return extensionFactory; } - + /** * The idle timeout in milliseconds */ @@ -343,43 +347,43 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { return connection.getMaxIdleTimeout(); } - + private Throwable getInvokedCause(Throwable t) { if (t instanceof FunctionCallException) { Throwable cause = ((FunctionCallException) t).getInvokedCause(); - if(cause != null) + if (cause != null) return cause; } - + return t; } - + @Override public InetSocketAddress getLocalAddress() { return connection.getLocalAddress(); } - + @ManagedAttribute(readonly = true) public OutgoingFrames getOutgoingHandler() { return outgoingHandler; } - + @Override public WebSocketPolicy getPolicy() { return this.policy; } - + @Override public String getProtocolVersion() { return protocolVersion; } - + @Override public RemoteEndpoint getRemote() { @@ -387,44 +391,47 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem LOG.debug("{}.getRemote()", this.getClass().getSimpleName()); AtomicConnectionState.State state = connectionState.get(); - + if ((state == AtomicConnectionState.State.OPEN) || (state == AtomicConnectionState.State.CONNECTED)) { return remote; } - - throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]"); + + String err = String.format("RemoteEndpoint unavailable, current state [%s], expecting [%s or %s]", + state.name(), AtomicConnectionState.State.OPEN.name(), AtomicConnectionState.State.CONNECTED.name()); + + throw new WebSocketException(err); } - + @Override public InetSocketAddress getRemoteAddress() { return connection.getRemoteAddress(); } - + public URI getRequestURI() { return requestURI; } - + @Override public UpgradeRequest getUpgradeRequest() { return this.upgradeRequest; } - + @Override public UpgradeResponse getUpgradeResponse() { return this.upgradeResponse; } - + @Override public WebSocketSession getWebSocketSession() { return this; } - + @Override public int hashCode() { @@ -440,14 +447,14 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem @Override public void incomingFrame(Frame frame, FrameCallback callback) { - try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader)) + try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader)) { - if (connectionState.get() == AtomicConnectionState.State.OPEN) + if (connectionState.get() != AtomicConnectionState.State.CLOSED) { // For endpoints that want to see raw frames. // These are immutable. endpointFunctions.onFrame(frame); - + byte opcode = frame.getOpCode(); switch (opcode) { @@ -457,13 +464,21 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem if (connectionState.onClosing()) { - LOG.debug("ConnectionState: Transition to CLOSING"); + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: Transition to CLOSING"); CloseFrame closeframe = (CloseFrame) frame; closeInfo = new CloseInfo(closeframe, true); } + else if (connectionState.onClosed()) + { + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: Transition to CLOSED"); + connection.disconnect(); + } else { - LOG.debug("ConnectionState: {} - Close Frame Received", connectionState); + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: {} - Close Frame Received", connectionState); } if (closeInfo != null) @@ -474,14 +489,14 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem // let fill/parse continue callback.succeed(); - + return; } case OpCode.PING: { if (LOG.isDebugEnabled()) LOG.debug("PING: {}", BufferUtil.toDetailString(frame.getPayload())); - + ByteBuffer pongBuf; if (frame.hasPayload()) { @@ -493,17 +508,18 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { pongBuf = ByteBuffer.allocate(0); } - + endpointFunctions.onPing(frame.getPayload()); callback.succeed(); - + try { getRemote().sendPong(pongBuf); } catch (Throwable t) { - LOG.debug("Unable to send pong", t); + if (LOG.isDebugEnabled()) + LOG.debug("Unable to send pong", t); } break; } @@ -511,7 +527,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { if (LOG.isDebugEnabled()) LOG.debug("PONG: {}", BufferUtil.toDetailString(frame.getPayload())); - + endpointFunctions.onPong(frame.getPayload()); callback.succeed(); break; @@ -531,7 +547,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem endpointFunctions.onContinuation(frame, callback); if (activeMessageSink != null) activeMessageSink.accept(frame, callback); - + return; } default: @@ -562,7 +578,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { return this.connectionState.get() == AtomicConnectionState.State.OPEN; } - + @Override public boolean isSecure() { @@ -570,19 +586,19 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { throw new IllegalStateException("No valid UpgradeRequest yet"); } - + URI requestURI = upgradeRequest.getRequestURI(); - + return "wss".equalsIgnoreCase(requestURI.getScheme()); } - + public void notifyClose(int statusCode, String reason) { if (LOG.isDebugEnabled()) { LOG.debug("notifyClose({},{}) [{}]", statusCode, reason, getState()); } - + CloseInfo closeInfo = new CloseInfo(statusCode, reason); endpointFunctions.onClose(closeInfo); } @@ -590,7 +606,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem /** * Error Event. *

- * Can be seen from Session and Connection. + * Can be seen from Session and Connection. *

* * @param t the raw cause @@ -612,10 +628,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem if (openFuture != null && !openFuture.isDone()) openFuture.completeExceptionally(cause); - + // Forward Errors to User WebSocket Object endpointFunctions.onError(cause); - + if (cause instanceof NotUtf8Exception) { close(StatusCode.BAD_PAYLOAD, cause.getMessage(), onDisconnectCallback); @@ -636,7 +652,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { CloseException ce = (CloseException) cause; FrameCallback callback = EMPTY; - + // Force disconnect for protocol breaking status codes switch (ce.getStatusCode()) { @@ -645,6 +661,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem case StatusCode.BAD_PAYLOAD: case StatusCode.MESSAGE_TOO_LARGE: case StatusCode.POLICY_VIOLATION: + case StatusCode.SERVER_ERROR: { callback = onDisconnectCallback; } @@ -655,7 +672,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem else { LOG.warn("Unhandled Error (closing connection)", cause); - + // Exception on end-user WS-Endpoint. // Fast-fail & close connection with reason. int statusCode = StatusCode.SERVER_ERROR; @@ -669,6 +686,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem /** * Connection Disconnect Event + * * @param connection the connection */ @Override @@ -688,6 +706,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem /** * Connection Open Event + * * @param connection the connection */ @Override @@ -701,9 +720,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem public WebSocketRemoteEndpoint newRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoingFrames, BatchMode batchMode) { - return new WebSocketRemoteEndpoint(this,outgoingHandler,getBatchMode()); + return new WebSocketRemoteEndpoint(this, outgoingHandler, getBatchMode()); } - + /** * Open/Activate the session */ @@ -711,53 +730,78 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { if (LOG.isDebugEnabled()) LOG.debug("{}.open()", this.getClass().getSimpleName()); - + if (remote != null) { // already opened return; } - + try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader)) { // Upgrade success - if(connectionState.onConnected()) + if (connectionState.onConnected()) { + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: Transition to CONNECTED"); + // Connect remote remote = remoteEndpointFactory.newRemoteEndpoint(connection, outgoingHandler, getBatchMode()); if (LOG.isDebugEnabled()) LOG.debug("{}.open() remote={}", this.getClass().getSimpleName(), remote); - - // Open WebSocket - endpointFunctions.onOpen(this); - - // Open connection - if(connectionState.onOpen()) + + try { - // notify session listeners - try + // Open WebSocket + endpointFunctions.onOpen(this); + + // Open connection + if (connectionState.onOpen()) { if (LOG.isDebugEnabled()) - LOG.debug("{}.onSessionOpened()", containerScope.getClass().getSimpleName()); - containerScope.onSessionOpened(this); + LOG.debug("ConnectionState: Transition to OPEN"); + + // notify session listeners + try + { + if (LOG.isDebugEnabled()) + LOG.debug("{}.onSessionOpened()", containerScope.getClass().getSimpleName()); + containerScope.onSessionOpened(this); + } + catch (Throwable t) + { + LOG.ignore(t); + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("open -> {}", dump()); + } + + if (openFuture != null) + { + openFuture.complete(this); + } } - catch (Throwable t) - { - LOG.ignore(t); - } - - if (LOG.isDebugEnabled()) - { - LOG.debug("open -> {}", dump()); - } - - if (openFuture != null) - { - openFuture.complete(this); - } - - connection.fillInterested(); } + catch (Throwable t) + { + endpointFunctions.getLog().warn("Error during OPEN", t); + onError(new CloseException(StatusCode.SERVER_ERROR, t)); + } + + /* Perform fillInterested outside of onConnected / onOpen. + * + * This is to allow for 2 specific scenarios. + * + * 1) Fast Close + * When an end users WSEndpoint.onOpen() calls + * the Session.close() method. + * This is a state transition of CONNECTING -> CONNECTED -> CLOSING + * 2) Fast Fail + * When an end users WSEndpoint.onOpen() throws an Exception. + */ + connection.fillInterested(); } else { @@ -778,17 +822,17 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem onError(t); } } - + public void setExtensionFactory(ExtensionFactory extensionFactory) { this.extensionFactory = extensionFactory; } - + public void setFuture(CompletableFuture fut) { this.openFuture = fut; } - + /** * Set the timeout in milliseconds */ @@ -797,12 +841,12 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { connection.setMaxIdleTimeout(ms); } - + public void setOutgoingHandler(OutgoingFrames outgoing) { this.outgoingHandler = outgoing; } - + public void setUpgradeRequest(UpgradeRequest request) { this.upgradeRequest = request; @@ -824,18 +868,18 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem } } } - + public void setUpgradeResponse(UpgradeResponse response) { this.upgradeResponse = response; } - + @Override public SuspendToken suspend() { return connection.suspend(); } - + /** * @return the default (initial) value for the batching mode. */ @@ -843,7 +887,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { return this.batchmode; } - + @Override public String toString() { @@ -861,7 +905,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem sb.append(',').append(getConnection().getClass().getSimpleName()); if (getConnection() instanceof AbstractWebSocketConnection) { - if(isOpen() && remote != null) + if (isOpen() && remote != null) { sb.append(',').append(getRemoteAddress()); if (getPolicy().getBehavior() == WebSocketBehavior.SERVER) @@ -874,11 +918,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem sb.append(']'); return sb.toString(); } - + public interface Listener { void onOpened(WebSocketSession session); - + void onClosed(WebSocketSession session); } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/CommonEndpointFunctions.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/CommonEndpointFunctions.java index 2a4744f46a7..dfaf861716b 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/CommonEndpointFunctions.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/CommonEndpointFunctions.java @@ -79,6 +79,7 @@ public class CommonEndpointFunctions extends AbstractLifeCycl protected final WebSocketPolicy policy; protected final Executor executor; + protected Logger endpointLog; private T session; private Function onOpenFunction; private Function onCloseFunction; @@ -513,6 +514,16 @@ public class CommonEndpointFunctions extends AbstractLifeCycl return executor; } + public Logger getLog() + { + if(endpointLog == null) + { + endpointLog = Log.getLogger(endpoint.getClass()); + } + + return endpointLog; + } + public T getSession() { return session; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/EndpointFunctions.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/EndpointFunctions.java index 5c5cbb20814..295c03984fd 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/EndpointFunctions.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/function/EndpointFunctions.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.common.function; import java.nio.ByteBuffer; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.FrameCallback; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.CloseInfo; @@ -32,6 +33,8 @@ import org.eclipse.jetty.websocket.common.CloseInfo; */ public interface EndpointFunctions extends LifeCycle { + Logger getLog(); + void onOpen(T session); void onClose(CloseInfo close); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index feee2f9c1a2..6be7efa8c90 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -435,8 +435,10 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override public void resume() { - suspendToken.set(false); - fillAndParse(); + if (suspendToken.compareAndSet(true, false)) + { + fillAndParse(); + } } public boolean addListener(LogicalConnection.Listener listener) diff --git a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalFuzzer.java b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalFuzzer.java index 85a715b5b50..1f277381306 100644 --- a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalFuzzer.java +++ b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalFuzzer.java @@ -50,6 +50,7 @@ public class LocalFuzzer implements AutoCloseable public final LocalFuzzer.Provider provider; public final UnitGenerator generator; public final LocalConnector.LocalEndPoint endPoint; + public final HttpTester.Response upgradeResponse; public LocalFuzzer(LocalFuzzer.Provider provider) throws Exception { @@ -68,7 +69,7 @@ public class LocalFuzzer implements AutoCloseable LOG.debug("Request: {}", upgradeRequest); ByteBuffer upgradeRequestBytes = BufferUtil.toBuffer(upgradeRequest.toString(), StandardCharsets.UTF_8); this.endPoint = this.provider.newLocalConnection(); - performUpgrade(endPoint, upgradeRequestBytes); + this.upgradeResponse = performUpgrade(endPoint, upgradeRequestBytes); this.generator = new UnitGenerator(WebSocketPolicy.newClientPolicy()); } @@ -291,6 +292,27 @@ public class LocalFuzzer implements AutoCloseable endPoint.addInputEOF(); } + /** + * Generate a ByteBuffer for each frame, and submit each to + * {@link org.eclipse.jetty.server.LocalConnector.LocalEndPoint#addInput(ByteBuffer)} + * + * @param frames the list of frames to send + */ + public void sendFrames(WebSocketFrame ... frames) + { + boolean eof = false; + for (WebSocketFrame f : frames) + { + ByteBuffer buffer = generator.generate(f); + endPoint.addInput(buffer); + if (f.getOpCode() == OpCode.CLOSE) + eof = true; + } + + if (eof) + endPoint.addInputEOF(); + } + /** * Generate a ByteBuffer for each frame, and submit each to * {@link org.eclipse.jetty.server.LocalConnector.LocalEndPoint#addInput(ByteBuffer)} diff --git a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalServer.java b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalServer.java index 4f344d399b4..ea037467d36 100644 --- a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalServer.java +++ b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/LocalServer.java @@ -24,6 +24,7 @@ import java.util.Map; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; @@ -115,6 +116,14 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi return new LocalFuzzer(this, requestPath, upgradeRequest); } + protected Handler createRootHandler(Server server) throws Exception + { + servletContextHandler = new ServletContextHandler(server, "/", true, false); + servletContextHandler.setContextPath("/"); + configureServletContextHandler(servletContextHandler); + return servletContextHandler; + } + protected void configureServletContextHandler(ServletContextHandler context) throws Exception { /* override to change context handler */ @@ -166,10 +175,8 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi localConnector = new LocalConnector(server); server.addConnector(localConnector); - servletContextHandler = new ServletContextHandler(server, "/", true, false); - servletContextHandler.setContextPath("/"); - configureServletContextHandler(servletContextHandler); - server.setHandler(servletContextHandler); + Handler rootHandler = createRootHandler(server); + server.setHandler(rootHandler); // Start Server addBean(server); diff --git a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/Sha1Sum.java b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/Sha1Sum.java new file mode 100644 index 00000000000..1ed4aa41455 --- /dev/null +++ b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/Sha1Sum.java @@ -0,0 +1,111 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.toolchain.test.Hex; +import org.eclipse.jetty.toolchain.test.IO; +import org.junit.Assert; + +/** + * Calculate the sha1sum for various content + */ +public class Sha1Sum +{ + private static class NoOpOutputStream extends OutputStream + { + @Override + public void write(byte[] b) throws IOException + { + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + } + + @Override + public void flush() throws IOException + { + } + + @Override + public void close() throws IOException + { + } + + @Override + public void write(int b) throws IOException + { + } + } + + public static String calculate(File file) throws NoSuchAlgorithmException, IOException + { + return calculate(file.toPath()); + } + + public static String calculate(Path path) throws NoSuchAlgorithmException, IOException + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + try (InputStream in = Files.newInputStream(path,StandardOpenOption.READ); + NoOpOutputStream noop = new NoOpOutputStream(); + DigestOutputStream digester = new DigestOutputStream(noop,digest)) + { + IO.copy(in,digester); + return Hex.asHex(digest.digest()); + } + } + + public static String calculate(byte[] buf) throws NoSuchAlgorithmException + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + digest.update(buf); + return Hex.asHex(digest.digest()); + } + + public static String calculate(byte[] buf, int offset, int len) throws NoSuchAlgorithmException + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + digest.update(buf,offset,len); + return Hex.asHex(digest.digest()); + } + + public static String loadSha1(File sha1File) throws IOException + { + String contents = IO.readToString(sha1File); + Pattern pat = Pattern.compile("^[0-9A-Fa-f]*"); + Matcher mat = pat.matcher(contents); + Assert.assertTrue("Should have found HEX code in SHA1 file: " + sha1File,mat.find()); + return mat.group(); + } + +} diff --git a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/UnitExtensionStack.java b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/UnitExtensionStack.java new file mode 100644 index 00000000000..9388a22b0fc --- /dev/null +++ b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/UnitExtensionStack.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.FrameCallback; +import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; +import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory; +import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; + +public class UnitExtensionStack extends ExtensionStack +{ + public static UnitExtensionStack clientBased() + { + return policyBased(new WebSocketPolicy(WebSocketBehavior.CLIENT)); + } + + public static UnitExtensionStack serverBased() + { + return policyBased(new WebSocketPolicy(WebSocketBehavior.SERVER)); + } + + private static UnitExtensionStack policyBased(WebSocketPolicy policy) + { + SimpleContainerScope containerScope = new SimpleContainerScope(policy); + WebSocketExtensionFactory extensionFactory = new WebSocketExtensionFactory(containerScope); + return new UnitExtensionStack(extensionFactory); + } + + private UnitExtensionStack(WebSocketExtensionFactory extensionFactory) + { + super(extensionFactory); + } + + /** + * Process frames + */ + public BlockingQueue processIncoming(BlockingQueue framesQueue) + { + BlockingQueue processed = new LinkedBlockingDeque<>(); + setNextIncoming((frame, callback) -> + { + processed.offer(WebSocketFrame.copy(frame)); + callback.succeed(); + }); + + FrameCallback callback = new FrameCallback.Adapter(); + for (WebSocketFrame frame : framesQueue) + { + incomingFrame(frame, callback); + } + + setNextIncoming(null); + return processed; + } + + /** + * Process frames as if they are for an outgoing path + * + * @param frames the frames to process + * @return the processed frames (post extension stack) + */ + public List processOutgoing(List frames) + { + List captured = new ArrayList<>(); + setNextOutgoing((frame, callback, batchMode) -> + { + captured.add(WebSocketFrame.copy(frame)); + callback.succeed(); + }); + + FrameCallback callback = new FrameCallback.Adapter(); + for (WebSocketFrame frame : frames) + { + outgoingFrame(frame, callback, BatchMode.OFF); + } + + setNextOutgoing(null); + return captured; + } +} diff --git a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/WSServer.java b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/WSServer.java index 4fa3e1edfe3..582b9764fdc 100644 --- a/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/WSServer.java +++ b/jetty-websocket/websocket-tests/src/main/java/org/eclipse/jetty/websocket/tests/WSServer.java @@ -24,20 +24,15 @@ import static org.junit.Assert.assertThat; import java.io.File; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; -import java.util.Map; import org.eclipse.jetty.annotations.AnnotationConfiguration; -import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.plus.webapp.EnvConfiguration; import org.eclipse.jetty.plus.webapp.PlusConfiguration; -import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.toolchain.test.FS; @@ -54,24 +49,18 @@ import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebInfConfiguration; import org.eclipse.jetty.webapp.WebXmlConfiguration; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.common.Parser; /** * Utility to build out exploded directory WebApps, in the /target/tests/ directory, for testing out servers that use javax.websocket endpoints. *

* This is particularly useful when the WebSocket endpoints are discovered via the javax.websocket annotation scanning. */ -public class WSServer implements LocalFuzzer.Provider +public class WSServer extends LocalServer implements LocalFuzzer.Provider { private static final Logger LOG = Log.getLogger(WSServer.class); private final Path contextDir; private final String contextPath; - private final ByteBufferPool bufferPool = new MappedByteBufferPool(); - private Server server; - private URI serverUri; private ContextHandlerCollection contexts; - private LocalConnector localConnector; private Path webinf; private Path classesDir; @@ -182,103 +171,16 @@ public class WSServer implements LocalFuzzer.Provider } } - public void dump() - { - server.dumpStdErr(); - } - - public LocalConnector getLocalConnector() - { - return localConnector; - } - - @Override - public Parser newClientParser(Parser.Handler parserHandler) - { - return new Parser(WebSocketPolicy.newClientPolicy(), bufferPool, parserHandler); - } - - @Override - public LocalConnector.LocalEndPoint newLocalConnection() - { - return getLocalConnector().connect(); - } - - public LocalFuzzer newLocalFuzzer() throws Exception - { - return new LocalFuzzer(this); - } - - public LocalFuzzer newLocalFuzzer(CharSequence requestPath) throws Exception - { - return new LocalFuzzer(this, requestPath); - } - - public LocalFuzzer newLocalFuzzer(CharSequence requestPath, Map upgradeRequest) throws Exception - { - return new LocalFuzzer(this, requestPath, upgradeRequest); - } - - public URI getServerBaseURI() - { - return serverUri; - } - - public Server getServer() - { - return server; - } - public Path getWebAppDir() { return this.contextDir; } - public void start() throws Exception + @Override + protected Handler createRootHandler(Server server) throws Exception { - server = new Server(); - - // Main network connector - ServerConnector connector = new ServerConnector(server); - connector.setPort(0); - server.addConnector(connector); - - // Add Local Connector - localConnector = new LocalConnector(server); - server.addConnector(localConnector); - HandlerCollection handlers = new HandlerCollection(); contexts = new ContextHandlerCollection(); - handlers.addHandler(contexts); - server.setHandler(handlers); - - server.start(); - - String host = connector.getHost(); - if (host == null) - { - host = "localhost"; - } - int port = connector.getLocalPort(); - serverUri = new URI(String.format("ws://%s:%d%s/", host, port, contextPath)); - if (LOG.isDebugEnabled()) - LOG.debug("Server started on {}", serverUri); - } - - public void stop() - { - if (server == null) - { - return; - } - - try - { - server.stop(); - } - catch (Exception e) - { - e.printStackTrace(System.err); - } + return contexts; } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsLegacyTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsLegacyTest.java deleted file mode 100644 index 87cb8e9cb57..00000000000 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsLegacyTest.java +++ /dev/null @@ -1,204 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server; - -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertThat; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.URI; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import javax.servlet.ServletContext; - -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.util.DecoratedObjectFactory; -import org.eclipse.jetty.util.Decorator; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; -import org.eclipse.jetty.websocket.servlet.WebSocketCreator; -import org.eclipse.jetty.websocket.servlet.WebSocketServlet; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.eclipse.jetty.websocket.tests.Defaults; -import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -public class DecoratorsLegacyTest -{ - private static class DecoratorsSocket extends WebSocketAdapter - { - private final DecoratedObjectFactory objFactory; - - public DecoratorsSocket(DecoratedObjectFactory objFactory) - { - this.objFactory = objFactory; - } - - @Override - public void onWebSocketText(String message) - { - StringWriter str = new StringWriter(); - PrintWriter out = new PrintWriter(str); - - if (objFactory != null) - { - out.printf("Object is a DecoratedObjectFactory%n"); - List decorators = objFactory.getDecorators(); - out.printf("Decorators.size = [%d]%n", decorators.size()); - for (Decorator decorator : decorators) - { - out.printf(" decorator[] = %s%n", decorator.getClass().getName()); - } - } - else - { - out.printf("DecoratedObjectFactory is NULL%n"); - } - - getRemote().sendStringByFuture(str.toString()); - } - } - - private static class DecoratorsCreator implements WebSocketCreator - { - @Override - public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) - { - ServletContext servletContext = req.getHttpServletRequest().getServletContext(); - DecoratedObjectFactory objFactory = (DecoratedObjectFactory) servletContext.getAttribute(DecoratedObjectFactory.ATTR); - return new DecoratorsSocket(objFactory); - } - } - - public static class DecoratorsRequestServlet extends WebSocketServlet - { - private static final long serialVersionUID = 1L; - private final WebSocketCreator creator; - - public DecoratorsRequestServlet(WebSocketCreator creator) - { - this.creator = creator; - } - - @Override - public void configure(WebSocketServletFactory factory) - { - factory.setCreator(this.creator); - } - } - - @SuppressWarnings("deprecation") - private static class DummyLegacyDecorator implements org.eclipse.jetty.servlet.ServletContextHandler.Decorator - { - @Override - public T decorate(T o) - { - return o; - } - - @Override - public void destroy(Object o) - { - } - } - - private static SimpleServletServer server; - private static DecoratorsCreator decoratorsCreator; - - @BeforeClass - public static void startServer() throws Exception - { - decoratorsCreator = new DecoratorsCreator(); - server = new SimpleServletServer(new DecoratorsRequestServlet(decoratorsCreator)) - { - @SuppressWarnings("deprecation") - @Override - protected void configureServletContextHandler(ServletContextHandler context) - { - context.getObjectFactory().clear(); - // Add decorator in the legacy way - context.addDecorator(new DummyLegacyDecorator()); - } - }; - server.start(); - } - - @AfterClass - public static void stopServer() throws Exception - { - server.stop(); - } - - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - - @Test - public void testAccessRequestCookies() throws Exception - { - client.setMaxIdleTimeout(TimeUnit.SECONDS.toMillis(1)); - - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - // Request Info - clientSession.getRemote().sendString("info"); - - // Read message - String incomingMsg = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("DecoratedObjectFactory", incomingMsg, containsString("Object is a DecoratedObjectFactory")); - assertThat("decorators.size", incomingMsg, containsString("Decorators.size = [1]")); - assertThat("decorator type", incomingMsg, containsString("decorator[] = " + DummyLegacyDecorator.class.getName())); - - clientSession.close(); - } -} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsTest.java index 1e24f2e5a64..fe32059e77c 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DecoratorsTest.java @@ -18,14 +18,16 @@ package org.eclipse.jetty.websocket.tests.server; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; import java.io.PrintWriter; import java.io.StringWriter; -import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.concurrent.Future; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import javax.servlet.ServletContext; @@ -33,28 +35,34 @@ import javax.servlet.ServletContext; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.Decorator; -import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketAdapter; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +/** + * Test the {@link Decorator} features of the WebSocketServer + */ +@RunWith(Parameterized.class) public class DecoratorsTest { + private static final Logger LOG = Log.getLogger(DecoratorsTest.class); + private static class DecoratorsSocket extends WebSocketAdapter { private final DecoratedObjectFactory objFactory; @@ -84,6 +92,8 @@ public class DecoratorsTest { out.printf("DecoratedObjectFactory is NULL%n"); } + + LOG.debug(out.toString()); getRemote().sendStringByFuture(str.toString()); } @@ -130,73 +140,100 @@ public class DecoratorsTest { } } - - private static SimpleServletServer server; - private static DecoratorsCreator decoratorsCreator; - - @BeforeClass - public static void startServer() throws Exception + + @SuppressWarnings("deprecation") + private static class DummyLegacyDecorator implements org.eclipse.jetty.servlet.ServletContextHandler.Decorator { - decoratorsCreator = new DecoratorsCreator(); - server = new SimpleServletServer(new DecoratorsRequestServlet(decoratorsCreator)) + @Override + public T decorate(T o) + { + return o; + } + + @Override + public void destroy(Object o) + { + } + } + + private interface Case + { + void customize(ServletContextHandler context); + } + + @SuppressWarnings("deprecation") + @Parameterized.Parameters(name = "{0}") + public static Collection data() + { + List cases = new ArrayList<>(); + + cases.add(new Object[] { + "Legacy Usage", + (Case) (context) -> { + context.getObjectFactory().clear(); + // Add decorator in the legacy way + context.addDecorator(new DummyLegacyDecorator()); + }, + DummyLegacyDecorator.class + }); + + cases.add(new Object[] { + "Recommended Usage", + (Case) (context) -> { + // Add decorator in the new util way + context.getObjectFactory().clear(); + context.getObjectFactory().addDecorator(new DummyUtilDecorator()); + }, + DummyUtilDecorator.class + }); + + return cases; + } + + private SimpleServletServer server; + private Class expectedDecoratorClass; + + public DecoratorsTest(String testId, Case testcase, Class expectedDecoratorClass) throws Exception + { + LOG.debug("Testing {}", testId); + this.expectedDecoratorClass = expectedDecoratorClass; + server = new SimpleServletServer(new DecoratorsRequestServlet(new DecoratorsCreator())) { @Override protected void configureServletContextHandler(ServletContextHandler context) { - // Add decorator in the new util way - context.getObjectFactory().clear(); - context.getObjectFactory().addDecorator(new DummyUtilDecorator()); + super.configureServletContextHandler(context); + testcase.customize(context); } }; server.start(); } - @AfterClass - public static void stopServer() throws Exception + @After + public void stopServer() throws Exception { server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - @Test public void testAccessRequestCookies() throws Exception { - client.setMaxIdleTimeout(TimeUnit.SECONDS.toMillis(1)); - - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - // Request Info - clientSession.getRemote().sendString("info"); - - // Read message - String incomingMsg = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("DecoratedObjectFactory", incomingMsg, containsString("Object is a DecoratedObjectFactory")); - assertThat("decorators.size", incomingMsg, containsString("Decorators.size = [1]")); - assertThat("decorator type", incomingMsg, containsString("decorator[] = " + DummyUtilDecorator.class.getName())); - - clientSession.close(); + try (LocalFuzzer session = server.newLocalFuzzer("/")) + { + session.sendFrames( + new TextFrame().setPayload("info"), + new CloseInfo(StatusCode.NORMAL).asFrame() + ); + + BlockingQueue framesQueue = session.getOutputFrames(); + + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.TEXT)); + + String payload = frame.getPayloadAsUTF8(); + assertThat("Text - DecoratedObjectFactory", payload, containsString("Object is a DecoratedObjectFactory")); + assertThat("Text - decorators.size", payload, containsString("Decorators.size = [1]")); + assertThat("Text - decorator type", payload, containsString("decorator[] = " + this.expectedDecoratorClass.getName())); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FirefoxTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FirefoxTest.java index 8980e33a7d2..4f85c1d9df9 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FirefoxTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FirefoxTest.java @@ -18,92 +18,41 @@ package org.eclipse.jetty.websocket.tests.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.net.URI; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.Map; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.tests.Defaults; -import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.eclipse.jetty.websocket.tests.servlets.EchoServlet; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; +import org.eclipse.jetty.websocket.tests.UpgradeUtils; import org.junit.Test; -import org.junit.rules.TestName; -public class FirefoxTest +public class FirefoxTest extends AbstractLocalServerCase { - private static SimpleServletServer server; - - @BeforeClass - public static void startServer() throws Exception - { - server = new SimpleServletServer(new EchoServlet()); - server.start(); - } - - @AfterClass - public static void stopServer() throws Exception - { - server.stop(); - } - - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - @Test public void testConnectionKeepAlive() throws Exception { - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - // Odd Connection Header value seen in Firefox - upgradeRequest.setHeader("Connection", "keep-alive, Upgrade"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - List extensionConfigList = clientSession.getUpgradeResponse().getExtensions(); - assertThat("Client Upgrade Response.Extensions", extensionConfigList.size(), is(1)); - assertThat("Client Upgrade Response.Extensions[0]", extensionConfigList.get(0).toString(), containsString("x-webkit-deflate-frame")); - - // Message String msg = "this is an echo ... cho ... ho ... o"; - clientSession.getRemote().sendString(msg); + + List send = new ArrayList<>(); + send.add(new TextFrame().setPayload(msg)); + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - // Read message - String incomingMsg = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - Assert.assertThat("Incoming Message", incomingMsg, is(msg)); + List expect = new ArrayList<>(); + expect.add(new TextFrame().setPayload(msg)); + expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - clientSession.close(); + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + // Odd Connection Header value seen in Firefox + upgradeHeaders.put("Connection", "keep-alive, Upgrade"); + try (LocalFuzzer session = server.newLocalFuzzer("/", upgradeHeaders)) + { + session.sendBulk(send); + session.expect(expect); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FragmentExtensionTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FragmentExtensionTest.java index e82ccd775be..10f3f78feaf 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FragmentExtensionTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FragmentExtensionTest.java @@ -20,31 +20,31 @@ package org.eclipse.jetty.websocket.tests.server; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; -import java.net.URI; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.Map; +import java.util.concurrent.BlockingQueue; -import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.WebSocketConstants; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; +import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.WebSocketFrame; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; +import org.eclipse.jetty.websocket.tests.UnitExtensionStack; +import org.eclipse.jetty.websocket.tests.UpgradeUtils; import org.eclipse.jetty.websocket.tests.servlets.EchoServlet; -import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Assume; -import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; public class FragmentExtensionTest { @@ -63,24 +63,6 @@ public class FragmentExtensionTest server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - private String[] split(String str, int partSize) { int strLength = str.length(); @@ -98,36 +80,41 @@ public class FragmentExtensionTest @Test public void testFragmentExtension() throws Exception { - Assume.assumeTrue("Server has fragment registered", - server.getWebSocketServletFactory().getExtensionFactory().isAvailable("fragment")); + ExtensionFactory extensionFactory = server.getWebSocketServletFactory().getExtensionFactory(); + assertThat("Extension Factory", extensionFactory, notNullValue()); + Assume.assumeTrue("Server has fragment registered", extensionFactory.isAvailable("fragment")); int fragSize = 4; - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.addExtensions("fragment;maxLength=" + fragSize); - upgradeRequest.setSubProtocols("onConnect"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - List extensionConfigList = clientSession.getUpgradeResponse().getExtensions(); - assertThat("Client Upgrade Response.Extensions", extensionConfigList.size(), is(1)); - assertThat("Client Upgrade Response.Extensions[0]", extensionConfigList.get(0).toString(), containsString("fragment")); - - // Message String msg = "Sent as a long message that should be split"; - clientSession.getRemote().sendString(msg); - - // Read message - String parts[] = split(msg, fragSize); - for (int i = 0; i < parts.length; i++) + List send = new ArrayList<>(); + send.add(new TextFrame().setPayload(msg)); + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_EXTENSIONS, "fragment;maxLength=" + fragSize); + + try (LocalFuzzer session = server.newLocalFuzzer("/", upgradeHeaders)) { - WebSocketFrame frame = clientSocket.framesQueue.poll(); - Assert.assertThat("text[" + i + "].payload", frame.getPayloadAsUTF8(), is(parts[i])); + String negotiatedExtensions = session.upgradeResponse.get(WebSocketConstants.SEC_WEBSOCKET_EXTENSIONS); + + List extensionConfigList = ExtensionConfig.parseList(negotiatedExtensions); + assertThat("Client Upgrade Response.Extensions", extensionConfigList.size(), is(1)); + assertThat("Client Upgrade Response.Extensions[0]", extensionConfigList.get(0).toString(), containsString("fragment")); + + UnitExtensionStack extensionStack = UnitExtensionStack.clientBased(); + List outgoingFrames = extensionStack.processOutgoing(send); + session.sendBulk(outgoingFrames); + + BlockingQueue framesQueue = session.getOutputFrames(); + BlockingQueue incomingFrames = extensionStack.processIncoming(framesQueue); + + String parts[] = split(msg, fragSize); + for (int i = 0; i < parts.length; i++) + { + WebSocketFrame frame = incomingFrames.poll(); + Assert.assertThat("text[" + i + "].payload", frame.getPayloadAsUTF8(), is(parts[i])); + } } - - clientSession.close(); } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdentityExtensionTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdentityExtensionTest.java index 269b63f6a86..31199cee86a 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdentityExtensionTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdentityExtensionTest.java @@ -20,94 +20,82 @@ package org.eclipse.jetty.websocket.tests.server; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; -import java.net.URI; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.Map; +import java.util.concurrent.BlockingQueue; -import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.WebSocketConstants; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; +import org.eclipse.jetty.websocket.tests.UnitExtensionStack; +import org.eclipse.jetty.websocket.tests.UpgradeUtils; import org.eclipse.jetty.websocket.tests.servlets.EchoServlet; -import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Assume; -import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; public class IdentityExtensionTest { private static SimpleServletServer server; - + @BeforeClass public static void startServer() throws Exception { server = new SimpleServletServer(new EchoServlet()); server.start(); } - + @AfterClass public static void stopServer() throws Exception { server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - @Test(timeout = 60000) public void testIdentityExtension() throws Exception { - Assume.assumeTrue("Server has identity extension registered", - server.getWebSocketServletFactory().getExtensionFactory().isAvailable("identity")); - - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.addExtensions("identity;param=0"); - upgradeRequest.addExtensions("identity;param=1, identity ; param = '2' ; other = ' some = value '"); - upgradeRequest.setSubProtocols("onConnect"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - List extensionConfigList = clientSession.getUpgradeResponse().getExtensions(); - assertThat("Client Upgrade Response.Extensions", extensionConfigList.size(), is(3)); - assertThat("Client Upgrade Response.Extensions[0]", extensionConfigList.get(0).toString(), containsString("identity")); - - // Message - String msg = "Hello Identity"; - clientSession.getRemote().sendString(msg); - - // Read message - String incomingMsg = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - Assert.assertThat("Incoming Message", incomingMsg, is(msg)); - - clientSession.close(); + ExtensionFactory extensionFactory = server.getWebSocketServletFactory().getExtensionFactory(); + assertThat("Extension Factory", extensionFactory, notNullValue()); + Assume.assumeTrue("Server has identity registered", extensionFactory.isAvailable("identity")); + + List send = new ArrayList<>(); + send.add(new TextFrame().setPayload("Hello Identity")); + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_EXTENSIONS, "identity;param=0, identity;param=1, identity ; param = '2' ; other = ' some = value '"); + + try (LocalFuzzer session = server.newLocalFuzzer("/", upgradeHeaders)) + { + String negotiatedExtensions = session.upgradeResponse.get(WebSocketConstants.SEC_WEBSOCKET_EXTENSIONS); + + List extensionConfigList = ExtensionConfig.parseList(negotiatedExtensions); + assertThat("Client Upgrade Response.Extensions", extensionConfigList.size(), is(3)); + assertThat("Client Upgrade Response.Extensions[0]", extensionConfigList.get(0).toString(), containsString("identity")); + + UnitExtensionStack extensionStack = UnitExtensionStack.clientBased(); + List outgoingFrames = extensionStack.processOutgoing(send); + session.sendBulk(outgoingFrames); + + BlockingQueue framesQueue = session.getOutputFrames(); + BlockingQueue incomingFrames = extensionStack.processIncoming(framesQueue); + + WebSocketFrame frame = incomingFrames.poll(); + Assert.assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.TEXT)); + Assert.assertThat("Frame.text-payload", frame.getPayloadAsUTF8(), is("Hello Identity")); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdleTimeoutTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdleTimeoutTest.java index 9e58086b40d..3aa10614b6f 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdleTimeoutTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/IdleTimeoutTest.java @@ -18,25 +18,24 @@ package org.eclipse.jetty.websocket.tests.server; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; -import java.net.URI; -import java.util.concurrent.Future; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.junit.After; import org.junit.AfterClass; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -45,7 +44,7 @@ import org.junit.rules.TestName; public class IdleTimeoutTest { @WebSocket(maxIdleTime = 500) - public static class FastTimeoutRFCSocket extends RFC6455Socket + public static class FastTimeoutSocket { } @@ -55,11 +54,14 @@ public class IdleTimeoutTest @Override public void configure(WebSocketServletFactory factory) { - factory.register(FastTimeoutRFCSocket.class); + factory.register(FastTimeoutSocket.class); } } - private static SimpleServletServer server; + protected static SimpleServletServer server; + + @Rule + public TestName testname = new TestName(); @BeforeClass public static void startServer() throws Exception @@ -74,24 +76,6 @@ public class IdleTimeoutTest server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - /** * Test IdleTimeout on server. * @@ -100,32 +84,19 @@ public class IdleTimeoutTest @Test public void testIdleTimeout() throws Exception { - client.setMaxIdleTimeout(2500); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("onConnect"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - // This wait should be shorter than client timeout above, but - // longer than server timeout configured in FastTimeoutRFCSocket - // eg: websocket server endpoint timeout < this timeout < websocket client idle timeout - TimeUnit.MILLISECONDS.sleep(1000); - - // Write to server - // This action is possible, but does nothing. - // Server could be in a half-closed state at this point. - // Where the server read is closed (due to timeout), but the server write is still open. - // The server could not read this frame, if it is in this half closed state - clientSession.getRemote().sendString("Hello"); - - // Expect closure, as server should have timed out - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.SHUTDOWN, containsString("Timeout")); - - clientSession.close(); + try (LocalFuzzer session = server.newLocalFuzzer()) + { + // wait 1 second to allow timeout to fire off + TimeUnit.SECONDS.sleep(1); + + session.sendFrames(new TextFrame().setPayload("You shouldn't be there")); + + BlockingQueue framesQueue = session.getOutputFrames(); + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.CLOSE)); + CloseInfo closeInfo = new CloseInfo(frame); + assertThat("Close.statusCode", closeInfo.getStatusCode(), is(StatusCode.SHUTDOWN)); + assertThat("Close.reason", closeInfo.getReason(), containsString("Timeout")); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/RequestHeadersTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/RequestHeadersTest.java deleted file mode 100644 index 5b5797f000b..00000000000 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/RequestHeadersTest.java +++ /dev/null @@ -1,105 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server; - -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - -import java.net.HttpCookie; -import java.net.URI; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.tests.Defaults; -import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.eclipse.jetty.websocket.tests.servlets.EchoServlet; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -public class RequestHeadersTest -{ - private static SimpleServletServer server; - - @BeforeClass - public static void startServer() throws Exception - { - server = new SimpleServletServer(new EchoServlet()); - server.start(); - } - - @AfterClass - public static void stopServer() throws Exception - { - server.stop(); - } - - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - - @Test - public void testAccessRequestCookies() throws Exception - { - URI wsUri = server.getServerUri(); - client.setMaxIdleTimeout(1000); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setHeader("Cookie", "fruit=Pear; type=Anjou"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - List cookies = clientSession.getUpgradeRequest().getCookies(); - Assert.assertThat("Request cookies", cookies, notNullValue()); - Assert.assertThat("Request cookies.size", cookies.size(), is(2)); - for (HttpCookie cookie : cookies) - { - Assert.assertThat("Cookie name", cookie.getName(), anyOf(is("fruit"), is("type"))); - Assert.assertThat("Cookie value", cookie.getValue(), anyOf(is("Pear"), is("Anjou"))); - } - clientSession.close(); - } -} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SuspendResumeTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SuspendResumeTest.java index 2d6d2329f83..7048126cc46 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SuspendResumeTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SuspendResumeTest.java @@ -18,11 +18,8 @@ package org.eclipse.jetty.websocket.tests.server; -import static org.hamcrest.Matchers.is; - -import java.net.URI; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.SuspendToken; @@ -30,24 +27,20 @@ import org.eclipse.jetty.websocket.api.WriteCallback; 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.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.CloseFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; public class SuspendResumeTest { @@ -69,7 +62,6 @@ public class SuspendResumeTest this.session.getRemote().sendString(message, new WriteCallback() { - @Override public void writeSuccess() { @@ -94,7 +86,7 @@ public class SuspendResumeTest } } - public static class EchoServlet extends WebSocketServlet + public static class BackPressureServlet extends WebSocketServlet { private static final long serialVersionUID = 1L; @@ -110,7 +102,7 @@ public class SuspendResumeTest @BeforeClass public static void startServer() throws Exception { - server = new SimpleServletServer(new EchoServlet()); + server = new SimpleServletServer(new BackPressureServlet()); server.start(); } @@ -120,46 +112,43 @@ public class SuspendResumeTest server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - @Test public void testSuspendResume() throws Exception { - URI wsUri = server.getServerUri(); + List send = new ArrayList<>(); + send.add(new TextFrame().setPayload("echo1")); + send.add(new TextFrame().setPayload("echo2")); + send.add(new CloseFrame()); - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); + List expect = new ArrayList<>(); + expect.add(new TextFrame().setPayload("echo1")); + expect.add(new TextFrame().setPayload("echo2")); + expect.add(new CloseFrame()); - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + try (LocalFuzzer session = server.newLocalFuzzer()) + { + session.sendBulk(send); + session.expect(expect); + } + } + + @Test + public void testSuspendResume_SmallBuffers() throws Exception + { + List send = new ArrayList<>(); + send.add(new TextFrame().setPayload("echo1")); + send.add(new TextFrame().setPayload("echo2")); + send.add(new CloseFrame()); - // Message - clientSession.getRemote().sendString("echo1"); - clientSession.getRemote().sendString("echo2"); + List expect = new ArrayList<>(); + expect.add(new TextFrame().setPayload("echo1")); + expect.add(new TextFrame().setPayload("echo2")); + expect.add(new CloseFrame()); - // Read message - String incomingMsg; - incomingMsg = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - Assert.assertThat("Incoming Message 1", incomingMsg, is("echo1")); - incomingMsg = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - Assert.assertThat("Incoming Message 2", incomingMsg, is("echo2")); - - clientSession.close(); + try (LocalFuzzer session = server.newLocalFuzzer()) + { + session.sendSegmented(send, 2); + session.expect(expect); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketCloseTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketCloseTest.java index fe411fb7d2f..f11b0117d2c 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketCloseTest.java @@ -18,352 +18,297 @@ package org.eclipse.jetty.websocket.tests.server; -import static org.hamcrest.Matchers.anything; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; -import java.net.URI; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; +import java.util.Map; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketAdapter; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.api.WebSocketConstants; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.server.WebSocketServerFactory; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; +import org.eclipse.jetty.websocket.tests.UpgradeUtils; +import org.hamcrest.Matchers; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; /** * Tests various close scenarios */ public class WebSocketCloseTest { - static class AbstractCloseSocket extends WebSocketAdapter + /** + * On Message, return container information + */ + public static class ContainerSocket extends WebSocketAdapter { - public CountDownLatch closeLatch = new CountDownLatch(1); - public String closeReason = null; - public int closeStatusCode = -1; - public List errors = new ArrayList<>(); - - @Override - public void onWebSocketClose(int statusCode, String reason) + private static final Logger LOG = Log.getLogger(WebSocketCloseTest.ContainerSocket.class); + private final WebSocketServerFactory container; + private Session session; + + public ContainerSocket(WebSocketServerFactory container) { - LOG.debug("onWebSocketClose({}, {})",statusCode,reason); - this.closeStatusCode = statusCode; - this.closeReason = reason; - closeLatch.countDown(); + this.container = container; } - + @Override - public void onWebSocketError(Throwable cause) + public void onWebSocketText(String message) { - errors.add(cause); + LOG.debug("onWebSocketText({})", message); + if (message.equalsIgnoreCase("openSessions")) + { + try + { + Collection sessions = container.getOpenSessions(); + + StringBuilder ret = new StringBuilder(); + ret.append("openSessions.size=").append(sessions.size()).append('\n'); + int idx = 0; + for (WebSocketSession sess : sessions) + { + ret.append('[').append(idx++).append("] ").append(sess.toString()).append('\n'); + } + session.getRemote().sendString(ret.toString()); + } + catch (IOException e) + { + LOG.warn(e); + } + } + session.close(StatusCode.NORMAL, "ContainerSocket"); + } + + @Override + public void onWebSocketConnect(Session sess) + { + LOG.debug("onWebSocketConnect({})", sess); + this.session = sess; } } - - @SuppressWarnings("serial") + + /** + * On Connect, close socket + */ + public static class FastCloseSocket extends WebSocketAdapter + { + private static final Logger LOG = Log.getLogger(WebSocketCloseTest.FastCloseSocket.class); + + @Override + public void onWebSocketConnect(Session sess) + { + LOG.debug("onWebSocketConnect({})", sess); + sess.close(StatusCode.NORMAL, "FastCloseServer"); + } + } + + /** + * On Connect, throw unhandled exception + */ + public static class FastFailSocket extends WebSocketAdapter + { + private static final Logger LOG = Log.getLogger(WebSocketCloseTest.FastFailSocket.class); + + @Override + public void onWebSocketConnect(Session sess) + { + LOG.debug("onWebSocketConnect({})", sess); + // Test failure due to unhandled exception + // this should trigger a fast-fail closure during open/connect + throw new RuntimeException("Intentional FastFail"); + } + } + + /** + * On Message, drop connection + */ + public static class DropServerConnectionSocket extends WebSocketAdapter + { + @Override + public void onWebSocketText(String message) + { + try + { + getSession().disconnect(); + } + catch (IOException ignore) + { + } + } + } + public static class CloseServlet extends WebSocketServlet implements WebSocketCreator { private WebSocketServerFactory serverFactory; - + @Override public void configure(WebSocketServletFactory factory) { factory.setCreator(this); if (factory instanceof WebSocketServerFactory) { - this.serverFactory = (WebSocketServerFactory)factory; + this.serverFactory = (WebSocketServerFactory) factory; } } - + @Override public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { if (req.hasSubProtocol("fastclose")) { - closeSocket = new FastCloseSocket(); - return closeSocket; + return new FastCloseSocket(); } - + if (req.hasSubProtocol("fastfail")) { - closeSocket = new FastFailSocket(); - return closeSocket; + return new FastFailSocket(); } - + + if (req.hasSubProtocol("drop")) + { + return new DropServerConnectionSocket(); + } + if (req.hasSubProtocol("container")) { - closeSocket = new ContainerSocket(serverFactory); - return closeSocket; + return new ContainerSocket(serverFactory); } + return new RFC6455Socket(); } } - - /** - * On Message, return container information - */ - public static class ContainerSocket extends AbstractCloseSocket - { - private static final Logger LOG = Log.getLogger(WebSocketCloseTest.ContainerSocket.class); - private final WebSocketServerFactory container; - private Session session; - - public ContainerSocket(WebSocketServerFactory container) - { - this.container = container; - } - - @Override - public void onWebSocketText(String message) - { - LOG.debug("onWebSocketText({})",message); - if (message.equalsIgnoreCase("openSessions")) - { - Collection sessions = container.getOpenSessions(); - - StringBuilder ret = new StringBuilder(); - ret.append("openSessions.size=").append(sessions.size()).append('\n'); - int idx = 0; - for (WebSocketSession sess : sessions) - { - ret.append('[').append(idx++).append("] ").append(sess.toString()).append('\n'); - } - session.getRemote().sendStringByFuture(ret.toString()); - } - session.close(StatusCode.NORMAL,"ContainerSocket"); - } - - @Override - public void onWebSocketConnect(Session sess) - { - LOG.debug("onWebSocketConnect({})",sess); - this.session = sess; - } - } - - /** - * On Connect, close socket - */ - public static class FastCloseSocket extends AbstractCloseSocket - { - private static final Logger LOG = Log.getLogger(WebSocketCloseTest.FastCloseSocket.class); - - @Override - public void onWebSocketConnect(Session sess) - { - LOG.debug("onWebSocketConnect({})",sess); - sess.close(StatusCode.NORMAL,"FastCloseServer"); - } - } - - /** - * On Connect, throw unhandled exception - */ - public static class FastFailSocket extends AbstractCloseSocket - { - private static final Logger LOG = Log.getLogger(WebSocketCloseTest.FastFailSocket.class); - - @Override - public void onWebSocketConnect(Session sess) - { - LOG.debug("onWebSocketConnect({})",sess); - // Test failure due to unhandled exception - // this should trigger a fast-fail closure during open/connect - throw new RuntimeException("Intentional FastFail"); - } - } - - private static final Logger LOG = Log.getLogger(WebSocketCloseTest.class); - - private static SimpleServletServer server; - private static AbstractCloseSocket closeSocket; - - @BeforeClass - public static void startServer() throws Exception + + private SimpleServletServer server; + + @Before + public void startServer() throws Exception { server = new SimpleServletServer(new CloseServlet()); server.start(); } - - @AfterClass - public static void stopServer() throws Exception + + @After + public void stopServer() throws Exception { server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - /** * Test fast close (bug #403817) - * - * @throws Exception - * on test failure + * + * @throws Exception on test failure */ @Test - public void testFastClose() throws Exception + public void fastClose() throws Exception { - client.setMaxIdleTimeout(5000); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("fastclose"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.NORMAL, anything()); + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, "fastclose"); - clientSession.close(); + List expect = new ArrayList<>(); + expect.add(new CloseInfo(StatusCode.NORMAL, "FastCloseServer").asFrame()); + + try (LocalFuzzer session = server.newLocalFuzzer("/", upgradeHeaders)) + { + session.sendFrames(new CloseInfo(StatusCode.NORMAL).asFrame()); + session.expect(expect); + } } - + /** * Test fast fail (bug #410537) - * - * @throws Exception - * on test failure + * + * @throws Exception on test failure */ @Test - public void testFastFail() throws Exception + public void fastFail() throws Exception { - client.setMaxIdleTimeout(1000); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("fastfail"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.SERVER_ERROR, anything()); - - clientSession.close(); + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, "fastfail"); + + List expect = new ArrayList<>(); + expect.add(new CloseInfo(StatusCode.SERVER_ERROR).asFrame()); + + try (StacklessLogging ignore = new StacklessLogging(FastFailSocket.class); + LocalFuzzer session = server.newLocalFuzzer("/", upgradeHeaders)) + { + session.expect(expect); + } } - + + @Test + public void dropServerConnection() throws Exception + { + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, "drop"); + + try (LocalFuzzer session = server.newLocalFuzzer("/", upgradeHeaders)) + { + session.sendFrames(new TextFrame().setPayload("drop")); + BlockingQueue framesQueue = session.getOutputFrames(); + assertThat("No frames as output", framesQueue.size(), Matchers.is(0)); + } + } + /** * Test session open session cleanup (bug #474936) - * - * @throws Exception - * on test failure + * + * @throws Exception on test failure */ @Test public void testOpenSessionCleanup() throws Exception { fastFail(); fastClose(); - dropConnection(); - - client.setMaxIdleTimeout(1000); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("container"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + dropClientConnection(); - clientSession.getRemote().sendString("openSessions"); + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, "container"); - String incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("Incoming Message", incomingMessage, containsString("openSessions.size=1\n")); - - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.NORMAL, anything()); - } - - @SuppressWarnings("Duplicates") - private void fastClose() throws Exception - { - client.setMaxIdleTimeout(1000); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("fastclose"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.NORMAL, anything()); - - clientSession.close(); - } - - private void fastFail() throws Exception - { - client.setMaxIdleTimeout(1000); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("fastfail"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - clientSocket.awaitCloseEvent("Client"); - clientSocket.assertCloseInfo("Client", StatusCode.SERVER_ERROR, anything()); - - clientSession.close(); + try (LocalFuzzer session = server.newLocalFuzzer("/?openSessions", upgradeHeaders)) + { + session.sendFrames( + new TextFrame().setPayload("openSessions"), + new CloseInfo(StatusCode.NORMAL).asFrame() + ); + + BlockingQueue framesQueue = session.getOutputFrames(); + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.TEXT)); + assertThat("Frame.text-payload", frame.getPayloadAsUTF8(), containsString("openSessions.size=1\n")); + } } - @SuppressWarnings("Duplicates") - private void dropConnection() throws Exception + private void dropClientConnection() throws Exception { - client.setMaxIdleTimeout(1000); - URI wsUri = server.getServerUri(); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - upgradeRequest.setSubProtocols("container"); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - - clientSession.close(); + Map upgradeHeaders = UpgradeUtils.newDefaultUpgradeRequestHeaders(); + upgradeHeaders.put(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, "container"); + + try (LocalFuzzer ignored = server.newLocalFuzzer("/", upgradeHeaders)) + { + // do nothing, just let endpoint close + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketServerSessionTest.java index 46a8d489517..22912bc7911 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketServerSessionTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketServerSessionTest.java @@ -18,17 +18,11 @@ package org.eclipse.jetty.websocket.tests.server; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - import java.io.IOException; -import java.net.URI; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.BatchMode; @@ -37,26 +31,20 @@ import org.eclipse.jetty.websocket.api.Session; 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.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.CloseFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; import org.eclipse.jetty.websocket.tests.SimpleServletServer; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.junit.After; import org.junit.AfterClass; -import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; /** * Testing various aspects of the server side support for WebSocket {@link org.eclipse.jetty.websocket.api.Session} */ -@RunWith(AdvancedRunner.class) public class WebSocketServerSessionTest { public static class SessionServlet extends WebSocketServlet @@ -83,7 +71,7 @@ public class WebSocketServerSessionTest @OnWebSocketMessage public void onText(String message) { - LOG.debug("onText({})",message); + LOG.debug("onText({})", message); if (message == null) { return; @@ -125,24 +113,18 @@ public class WebSocketServerSessionTest if ("session.isSecure".equals(message)) { - String issecure = String.format("session.isSecure=%b",session.isSecure()); + String issecure = String.format("session.isSecure=%b", session.isSecure()); sendString(issecure); return; } if ("session.upgradeRequest.requestURI".equals(message)) { - String response = String.format("session.upgradeRequest.requestURI=%s",session.getUpgradeRequest().getRequestURI().toASCIIString()); + String response = String.format("session.upgradeRequest.requestURI=%s", session.getUpgradeRequest().getRequestURI().toASCIIString()); sendString(response); return; } - if ("harsh-disconnect".equals(message)) - { - session.disconnect(); - return; - } - // echo the message back. sendString(message); } @@ -162,77 +144,43 @@ public class WebSocketServerSessionTest } private static SimpleServletServer server; - + @BeforeClass public static void startServer() throws Exception { server = new SimpleServletServer(new SessionServlet()); server.start(); } - + @AfterClass public static void stopServer() throws Exception { server.stop(); } - @Rule - public TestName testname = new TestName(); - - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - - @Test - public void testDisconnect() throws Exception - { - URI wsUri = server.getServerUri().resolve("/test/disconnect"); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - clientSession.getRemote().sendString("harsh-disconnect"); - - // TODO: or onError(EOF) - clientSocket.awaitCloseEvent("Client"); - } - @Test public void testUpgradeRequestResponse() throws Exception { - URI wsUri = server.getServerUri().resolve("/test?snack=cashews&amount=handful&brand=off"); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - clientSession.getRemote().sendString("getParameterMap|snack"); - clientSession.getRemote().sendString("getParameterMap|amount"); - clientSession.getRemote().sendString("getParameterMap|brand"); - clientSession.getRemote().sendString("getParameterMap|cost"); + String requestPath = "/test?snack=cashews&amount=handful&brand=off"; - String incomingMessage; - incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("Parameter Map[snack]", incomingMessage, is("[cashews]")); - incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("Parameter Map[amount]", incomingMessage, is("[handful]")); - incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("Parameter Map[brand]", incomingMessage, is("[off]")); - incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("Parameter Map[cost]", incomingMessage, is("")); + List send = new ArrayList<>(); + send.add(new TextFrame().setPayload("getParameterMap|snack")); + send.add(new TextFrame().setPayload("getParameterMap|amount")); + send.add(new TextFrame().setPayload("getParameterMap|brand")); + send.add(new TextFrame().setPayload("getParameterMap|cost")); + send.add(new CloseFrame()); + + List expect = new ArrayList<>(); + expect.add(new TextFrame().setPayload("[cashews]")); + expect.add(new TextFrame().setPayload("[handful]")); + expect.add(new TextFrame().setPayload("[off]")); + expect.add(new TextFrame().setPayload("")); + send.add(new CloseFrame()); + + try (LocalFuzzer session = server.newLocalFuzzer(requestPath)) + { + session.sendFrames(send); + session.expect(expect); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterEmbeddedTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterEmbeddedTest.java new file mode 100644 index 00000000000..01c5b12150c --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterEmbeddedTest.java @@ -0,0 +1,134 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; +import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.tests.LocalServer; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class WebSocketUpgradeFilterEmbeddedTest extends WebSocketUpgradeFilterTest +{ + private interface Case + { + void customize(ServletContextHandler context) throws Exception; + } + + @Parameterized.Parameters(name = "{0}") + public static List data() + { + final WebSocketCreator infoCreator = (req, resp) -> new InfoSocket(); + + List cases = new ArrayList<>(); + + // Embedded WSUF.configureContext(), directly app-ws configuration + + cases.add(new Object[] + {"wsuf.configureContext/Direct configure", (Case) (context) -> + { + WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context); + // direct configuration via WSUF + wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + wsuf.addMapping("/info/*", infoCreator); + }}); + + // Embedded WSUF.configureContext(), apply app-ws configuration via attribute + + cases.add(new Object[]{ + "wsuf.configureContext/Attribute based configure", (Case) (context) -> + { + WebSocketUpgradeFilter.configureContext(context); + + // configuration via attribute + NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName()); + assertThat("NativeWebSocketConfiguration", configuration, notNullValue()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping("/info/*", infoCreator); + }}); + + // Embedded WSUF, added as filter, apply app-ws configuration via attribute + + cases.add(new Object[]{ + "wsuf/addFilter/Attribute based configure", (Case) (context) -> + { + context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping("/info/*", infoCreator); + context.setAttribute(NativeWebSocketConfiguration.class.getName(), configuration); + }}); + + // Embedded WSUF, added as filter, apply app-ws configuration via wsuf constructor + + cases.add(new Object[]{ + "wsuf/addFilter/WSUF Constructor configure", (Case) (context) -> + { + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping("/info/*", infoCreator); + context.addBean(configuration, true); + + FilterHolder wsufHolder = new FilterHolder(new WebSocketUpgradeFilter(configuration)); + context.addFilter(wsufHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); + }}); + + // Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener + + cases.add(new Object[]{ + "wsuf.configureContext/ServletContextListener configure", (Case) (context) -> + { + context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + context.addEventListener(new InfoContextListener()); + }}); + + return cases; + } + + public WebSocketUpgradeFilterEmbeddedTest(String testid, Case testcase) throws Exception + { + super(newServer(testcase)); + } + + private static LocalServer newServer(Case testcase) + { + return new LocalServer() + { + @Override + protected void configureServletContextHandler(ServletContextHandler context) throws Exception + { + testcase.customize(context); + } + }; + } +} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterTest.java index 9abb8258b01..938918c0155 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterTest.java @@ -18,361 +18,118 @@ package org.eclipse.jetty.websocket.tests.server; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import java.io.File; -import java.net.URI; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.concurrent.Future; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.DispatcherType; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.util.WSURI; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; -import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; -import org.eclipse.jetty.websocket.servlet.WebSocketCreator; -import org.eclipse.jetty.websocket.tests.Defaults; -import org.eclipse.jetty.websocket.tests.TrackingEndpoint; -import org.eclipse.jetty.websocket.tests.WSServer; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; +import org.eclipse.jetty.websocket.tests.LocalServer; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -@RunWith(Parameterized.class) -public class WebSocketUpgradeFilterTest +public abstract class WebSocketUpgradeFilterTest { - interface ServerProvider - { - Server newServer() throws Exception; - } - private static AtomicInteger uniqTestDirId = new AtomicInteger(0); - private static File getNewTestDir() + protected static File getNewTestDir() { - return MavenTestingUtils.getTargetTestingDir("WSUF-webxml-" + uniqTestDirId.getAndIncrement()); - } - - @Parameterized.Parameters(name = "{0}") - public static List data() - { - final WebSocketCreator infoCreator = (req, resp) -> new InfoSocket(); - - List cases = new ArrayList<>(); - - // Embedded WSUF.configureContext(), directly app-ws configuration - - cases.add(new Object[]{"wsuf.configureContext/Direct configure", (ServerProvider) () -> - { - Server server1 = new Server(); - ServerConnector connector = new ServerConnector(server1); - connector.setPort(0); - server1.addConnector(connector); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server1.setHandler(context); - - WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context); - - // direct configuration via WSUF - wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); - wsuf.addMapping("/info/*", infoCreator); - - server1.start(); - return server1; - }}); - - // Embedded WSUF.configureContext(), apply app-ws configuration via attribute - - cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", (ServerProvider) () -> - { - Server server12 = new Server(); - ServerConnector connector = new ServerConnector(server12); - connector.setPort(0); - server12.addConnector(connector); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server12.setHandler(context); - - WebSocketUpgradeFilter.configureContext(context); - - // configuration via attribute - NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName()); - assertThat("NativeWebSocketConfiguration", configuration, notNullValue()); - configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); - configuration.addMapping("/info/*", infoCreator); - - server12.start(); - - return server12; - }}); - - // Embedded WSUF, added as filter, apply app-ws configuration via attribute - - cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", (ServerProvider) () -> - { - Server server13 = new Server(); - ServerConnector connector = new ServerConnector(server13); - connector.setPort(0); - server13.addConnector(connector); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server13.setHandler(context); - context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - - NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext()); - configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); - configuration.addMapping("/info/*", infoCreator); - context.setAttribute(NativeWebSocketConfiguration.class.getName(), configuration); - - server13.start(); - - return server13; - }}); - - // Embedded WSUF, added as filter, apply app-ws configuration via wsuf constructor - - cases.add(new Object[]{"wsuf/addFilter/WSUF Constructor configure", new ServerProvider() - { - @Override - public Server newServer() throws Exception - { - Server server = new Server(); - ServerConnector connector = new ServerConnector(server); - connector.setPort(0); - server.addConnector(connector); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server.setHandler(context); - - NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext()); - configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); - configuration.addMapping("/info/*", infoCreator); - context.addBean(configuration, true); - - FilterHolder wsufHolder = new FilterHolder(new WebSocketUpgradeFilter(configuration)); - context.addFilter(wsufHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); - - server.start(); - - return server; - } - }}); - - // Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener - - cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", (ServerProvider) () -> - { - Server server14 = new Server(); - ServerConnector connector = new ServerConnector(server14); - connector.setPort(0); - server14.addConnector(connector); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server14.setHandler(context); - context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - context.addEventListener(new InfoContextListener()); - - server14.start(); - - return server14; - }}); - - // WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener - - cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", (ServerProvider) () -> - { - File testDir = getNewTestDir(); - - WSServer server15 = new WSServer(testDir, "/"); - - server15.copyWebInf("wsuf-config-via-listener.xml"); - server15.copyClass(InfoSocket.class); - server15.copyClass(InfoContextAttributeListener.class); - server15.start(); - - WebAppContext webapp = server15.createWebAppContext(); - server15.deployWebapp(webapp); - - return server15.getServer(); - }}); - - // WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener with WEB-INF/lib/jetty-http.jar - - cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/jetty-http.jar", new ServerProvider() - { - @Override - public Server newServer() throws Exception - { - File testDir = getNewTestDir(); - - WSServer server = new WSServer(testDir, "/"); - - server.copyWebInf("wsuf-config-via-listener.xml"); - server.copyClass(InfoSocket.class); - server.copyClass(InfoContextAttributeListener.class); - // Add a jetty-http.jar to ensure that the classloader constraints - // and the WebAppClassloader setup is sane and correct - // The odd version string is present to capture bad regex behavior in Jetty - server.copyLib(org.eclipse.jetty.http.pathmap.PathSpec.class, "jetty-http-9.99.999.jar"); - server.start(); - - WebAppContext webapp = server.createWebAppContext(); - server.deployWebapp(webapp); - - return server.getServer(); - } - }}); - - // WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init - - cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", (ServerProvider) () -> - { - File testDir = getNewTestDir(); - - WSServer server16 = new WSServer(testDir, "/"); - - server16.copyWebInf("wsuf-config-via-servlet-init.xml"); - server16.copyClass(InfoSocket.class); - server16.copyClass(InfoServlet.class); - server16.start(); - - WebAppContext webapp = server16.createWebAppContext(); - server16.deployWebapp(webapp); - - return server16.getServer(); - }}); - - // xml based, wsuf, on alternate url-pattern and config attribute location - - cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", (ServerProvider) () -> - { - File testDir = getNewTestDir(); - - WSServer server17 = new WSServer(testDir, "/"); - - server17.copyWebInf("wsuf-alt-config-via-listener.xml"); - server17.copyClass(InfoSocket.class); - server17.copyClass(InfoContextAltAttributeListener.class); - server17.start(); - - WebAppContext webapp = server17.createWebAppContext(); - server17.deployWebapp(webapp); - - return server17.getServer(); - }}); - - return cases; + File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml-" + uniqTestDirId.getAndIncrement()); + FS.ensureDirExists(testDir); + return testDir; } @Rule public TestName testname = new TestName(); - private WebSocketClient client; + private LocalServer server; - @Before - public void startClient() throws Exception + public WebSocketUpgradeFilterTest(LocalServer server) throws Exception { - client = new WebSocketClient(); - client.start(); + this.server = server; + this.server.start(); } @After - public void stopClient() throws Exception + public void stopServer() throws Exception { - client.stop(); - } - - private final Server server; - private final URI serverUri; - - public WebSocketUpgradeFilterTest(String testId, ServerProvider serverProvider) throws Exception - { - this.server = serverProvider.newServer(); - serverUri = WSURI.toWebsocket(server.getURI()); + server.stop(); } @Test public void testNormalConfiguration() throws Exception { - URI wsUri = serverUri.resolve("/info/"); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - clientSession.getRemote().sendString("hello"); - - String incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - - // If we can connect and send a text message, we know that the endpoint was - // added properly, and the response will help us verify the policy configuration too - assertThat("Client incoming message", incomingMessage, containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); + try (LocalFuzzer session = server.newLocalFuzzer("/info/")) + { + session.sendFrames( + new TextFrame().setPayload("hello"), + new CloseInfo(StatusCode.NORMAL).asFrame() + ); + + BlockingQueue framesQueue = session.getOutputFrames(); + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.TEXT)); + + // If we can connect and send a text message, we know that the endpoint was + // added properly, and the response will help us verify the policy configuration too + assertThat("Frame.text-payload", frame.getPayloadAsUTF8(), containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); + } } @Test public void testStopStartOfHandler() throws Exception { - URI wsUri = serverUri.resolve("/info/"); - - TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); - ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); - Future clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - clientSession.getRemote().sendString("hello 1"); - - String incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - - // If we can connect and send a text message, we know that the endpoint was - // added properly, and the response will help us verify the policy configuration too - assertThat("Client incoming message", incomingMessage, containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); - - clientSession.close(); + try (LocalFuzzer session = server.newLocalFuzzer("/info/")) + { + session.sendFrames( + new TextFrame().setPayload("hello 1"), + new CloseInfo(StatusCode.NORMAL).asFrame() + ); + + BlockingQueue framesQueue = session.getOutputFrames(); + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.TEXT)); + + // If we can connect and send a text message, we know that the endpoint was + // added properly, and the response will help us verify the policy configuration too + assertThat("Frame.text-payload", frame.getPayloadAsUTF8(), containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); + } - server.getHandler().stop(); - server.getHandler().start(); + server.getServletContextHandler().stop(); + server.getServletContextHandler().start(); // Make request again (server should have retained websocket configuration) - - clientSocket = new TrackingEndpoint(testname.getMethodName()); - upgradeRequest = new ClientUpgradeRequest(); - clientConnectFuture = client.connect(clientSocket, wsUri, upgradeRequest); - - clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - clientSession.getRemote().sendString("hello 2"); - - incomingMessage = clientSocket.messageQueue.poll(5, TimeUnit.SECONDS); - - // If we can connect and send a text message, we know that the endpoint was - // added properly, and the response will help us verify the policy configuration too - assertThat("Client incoming message", incomingMessage, containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); - - clientSession.close(); + + try (LocalFuzzer session = server.newLocalFuzzer("/info/")) + { + session.sendFrames( + new TextFrame().setPayload("hello 2"), + new CloseInfo(StatusCode.NORMAL).asFrame() + ); + + BlockingQueue framesQueue = session.getOutputFrames(); + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.TEXT)); + + // If we can connect and send a text message, we know that the endpoint was + // added properly, and the response will help us verify the policy configuration too + assertThat("Frame.text-payload", frame.getPayloadAsUTF8(), containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); + } } } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterWebappTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterWebappTest.java new file mode 100644 index 00000000000..b32a96afddc --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/WebSocketUpgradeFilterWebappTest.java @@ -0,0 +1,111 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.tests.WSServer; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class WebSocketUpgradeFilterWebappTest extends WebSocketUpgradeFilterTest +{ + private interface Case + { + void customize(WSServer server) throws Exception; + } + + @Parameterized.Parameters(name = "{0}") + public static List data() + { + List cases = new ArrayList<>(); + + // WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener + + cases.add(new Object[]{ + "From ServletContextListener", + (Case) (server) -> + { + server.copyWebInf("wsuf-config-via-listener.xml"); + server.copyClass(InfoSocket.class); + server.copyClass(InfoContextAttributeListener.class); + } + }); + + // WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener with WEB-INF/lib/jetty-http.jar + + cases.add(new Object[]{ + "From ServletContextListener with jar scanning", + (Case) (server) -> + { + server.copyWebInf("wsuf-config-via-listener.xml"); + server.copyClass(InfoSocket.class); + server.copyClass(InfoContextAttributeListener.class); + // Add a jetty-http.jar to ensure that the classloader constraints + // and the WebAppClassloader setup is sane and correct + // The odd version string is present to capture bad regex behavior in Jetty + server.copyLib(org.eclipse.jetty.http.pathmap.PathSpec.class, "jetty-http-9.99.999.jar"); + } + }); + + return cases; + } + + public WebSocketUpgradeFilterWebappTest(String testid, Case testcase) throws Exception + { + super(newServer(testcase)); + } + + private static WSServer newServer(Case testcase) + { + return new WSServer(getNewTestDir(), "") + { + private WebAppContext webapp; + + @Override + protected Handler createRootHandler(Server server) throws Exception + { + Handler handler = super.createRootHandler(server); + testcase.customize(this); + return handler; + } + + @Override + public ServletContextHandler getServletContextHandler() + { + return this.webapp; + } + + @Override + protected void doStart() throws Exception + { + super.doStart(); + + this.webapp = createWebAppContext(); + deployWebapp(webapp); + } + }; + } +} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/AnnotatedServerEndpointTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/AnnotatedServerEndpointTest.java index 320fa199476..9b24ac57209 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/AnnotatedServerEndpointTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/AnnotatedServerEndpointTest.java @@ -71,7 +71,7 @@ public class AnnotatedServerEndpointTest } @AfterClass - public static void stopServer() + public static void stopServer() throws Exception { server.stop(); } diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutContextListener.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutContextListener.java new file mode 100644 index 00000000000..985ddcc1ac8 --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutContextListener.java @@ -0,0 +1,56 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server.jsr356; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.jetty.websocket.tests.server.jsr356.sockets.IdleTimeoutOnOpenEndpoint; + +/** + * Example of adding a server WebSocket (extending {@link javax.websocket.Endpoint}) programmatically via config + */ +public class IdleTimeoutContextListener implements ServletContextListener +{ + @Override + public void contextDestroyed(ServletContextEvent sce) + { + /* do nothing */ + } + + @Override + public void contextInitialized(ServletContextEvent sce) + { + ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName()); + // Build up a configuration with a specific path + String path = "/idle-onopen-endpoint"; + ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(IdleTimeoutOnOpenEndpoint.class,path); + try + { + container.addEndpoint(builder.build()); + } + catch (DeploymentException e) + { + throw new RuntimeException("Unable to add endpoint via config file",e); + } + } +} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutTest.java new file mode 100644 index 00000000000..c023b426335 --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/IdleTimeoutTest.java @@ -0,0 +1,106 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server.jsr356; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.tests.LeakTrackingBufferPoolRule; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; +import org.eclipse.jetty.websocket.tests.WSServer; +import org.eclipse.jetty.websocket.tests.server.jsr356.sockets.IdleTimeoutOnOpenEndpoint; +import org.eclipse.jetty.websocket.tests.server.jsr356.sockets.IdleTimeoutOnOpenSocket; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +public class IdleTimeoutTest +{ + @Rule + public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test"); + + private static WSServer server; + + @BeforeClass + public static void setupServer() throws Exception + { + server = new WSServer(MavenTestingUtils.getTargetTestingPath(IdleTimeoutTest.class.getName()), "app"); + server.copyWebInf("idle-timeout-config-web.xml"); + // the endpoint (extends javax.websocket.Endpoint) + server.copyClass(IdleTimeoutOnOpenEndpoint.class); + // the configuration that adds the endpoint + server.copyClass(IdleTimeoutContextListener.class); + // the annotated socket + server.copyClass(IdleTimeoutOnOpenSocket.class); + + server.start(); + + WebAppContext webapp = server.createWebAppContext(); + server.deployWebapp(webapp); + // wsb.dump(); + } + + @AfterClass + public static void stopServer() throws Exception + { + server.stop(); + } + + private void assertConnectionTimeout(String requestPath) throws Exception + { + try (LocalFuzzer session = server.newLocalFuzzer(requestPath)) + { + // wait 1 second to allow timeout to fire off + TimeUnit.SECONDS.sleep(1); + + session.sendFrames(new TextFrame().setPayload("You shouldn't be there")); + + BlockingQueue framesQueue = session.getOutputFrames(); + WebSocketFrame frame = framesQueue.poll(1, TimeUnit.SECONDS); + assertThat("Frame.opCode", frame.getOpCode(), is(OpCode.CLOSE)); + CloseInfo closeInfo = new CloseInfo(frame); + assertThat("Close.statusCode", closeInfo.getStatusCode(), is(StatusCode.SHUTDOWN)); + assertThat("Close.reason", closeInfo.getReason(), containsString("Timeout")); + } + } + + @Test + public void testAnnotated() throws Exception + { + assertConnectionTimeout("/app/idle-onopen-socket"); + } + + @Test + public void testEndpoint() throws Exception + { + assertConnectionTimeout("/app/idle-onopen-endpoint"); + } +} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/SessionTrackingTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/SessionTrackingTest.java new file mode 100644 index 00000000000..2d57ed7fd41 --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/SessionTrackingTest.java @@ -0,0 +1,173 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server.jsr356; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.frames.CloseFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.eclipse.jetty.websocket.tests.LocalFuzzer; +import org.eclipse.jetty.websocket.tests.SimpleServletServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SessionTrackingTest +{ + @WebSocket + public static class SessionTrackingSocket + { + private final WebSocketServerFactory container; + + public SessionTrackingSocket(WebSocketServerFactory container) + { + this.container = container; + } + + @OnWebSocketMessage + public void onMessage(Session session, String msg) throws IOException + { + if (msg == null) + { + session.getRemote().sendString("Unknown command: "); + return; + } + + String parts[] = msg.split("\\|"); + + if ("openSessions".equals(parts[0])) + { + Collection sessions = container.getOpenSessions(); + String ret = String.format("openSessions(@%s).size=%d", parts[1], sessions.size()); + session.getRemote().sendString(ret); + return; + } + + session.getRemote().sendString("Unknown command: " + msg); + } + } + + public static class SessionTrackingServlet extends WebSocketServlet implements WebSocketCreator + { + private WebSocketServerFactory serverFactory; + + @Override + public void configure(WebSocketServletFactory factory) + { + // If this fails, then we have a lot of tests failing. + this.serverFactory = (WebSocketServerFactory) factory; + factory.setCreator(this); + } + + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + { + return new SessionTrackingSocket(serverFactory); + } + } + + private static SimpleServletServer server; + + @BeforeClass + public static void startServer() throws Exception + { + server = new SimpleServletServer(new SessionTrackingServlet()); + server.start(); + } + + @AfterClass + public static void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testAddRemoveSessions() throws Exception + { + try (LocalFuzzer session1 = server.newLocalFuzzer("/1")) + { + sendTextFrameToAll("openSessions|in-1", session1); + + try (LocalFuzzer session2 = server.newLocalFuzzer("/2")) + { + sendTextFrameToAll("openSessions|in-2", session1, session2); + + try (LocalFuzzer session3 = server.newLocalFuzzer("/3")) + { + sendTextFrameToAll("openSessions|in-3", session1, session2, session3); + sendTextFrameToAll("openSessions|lvl-3", session1, session2, session3); + + session3.sendFrames(new CloseFrame()); + + List expect3 = new ArrayList<>(); + expect3.add(new TextFrame().setPayload("openSessions(@in-3).size=3")); + expect3.add(new TextFrame().setPayload("openSessions(@lvl-3).size=3")); + expect3.add(new CloseFrame()); + session3.expect(expect3); + } + + sendTextFrameToAll("openSessions|lvl-2", session1, session2); + session2.sendFrames(new CloseFrame()); + + List expect2 = new ArrayList<>(); + expect2.add(new TextFrame().setPayload("openSessions(@in-2).size=2")); + expect2.add(new TextFrame().setPayload("openSessions(@in-3).size=3")); + expect2.add(new TextFrame().setPayload("openSessions(@lvl-3).size=3")); + expect2.add(new TextFrame().setPayload("openSessions(@lvl-2).size=2")); + expect2.add(new CloseFrame()); + session2.expect(expect2); + } + + sendTextFrameToAll("openSessions|lvl-1", session1); + session1.sendFrames(new CloseFrame()); + + List expect1 = new ArrayList<>(); + expect1.add(new TextFrame().setPayload("openSessions(@in-1).size=1")); + expect1.add(new TextFrame().setPayload("openSessions(@in-2).size=2")); + expect1.add(new TextFrame().setPayload("openSessions(@in-3).size=3")); + expect1.add(new TextFrame().setPayload("openSessions(@lvl-3).size=3")); + expect1.add(new TextFrame().setPayload("openSessions(@lvl-2).size=2")); + expect1.add(new TextFrame().setPayload("openSessions(@lvl-1).size=1")); + expect1.add(new CloseFrame()); + session1.expect(expect1); + } + } + + private void sendTextFrameToAll(String msg, LocalFuzzer... sessions) + { + for (LocalFuzzer session : sessions) + { + session.sendFrames(new TextFrame().setPayload(msg)); + } + } +} diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/StreamTest.java similarity index 97% rename from jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java rename to jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/StreamTest.java index d5564479407..1a3c3fe444c 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/StreamTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.jsr356.server; +package org.eclipse.jetty.websocket.tests.server.jsr356; import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.is; @@ -58,9 +58,10 @@ import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule; -import org.eclipse.jetty.websocket.common.util.Sha1Sum; +import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; +import org.eclipse.jetty.websocket.tests.LeakTrackingBufferPoolRule; +import org.eclipse.jetty.websocket.tests.Sha1Sum; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenEndpoint.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenEndpoint.java new file mode 100644 index 00000000000..5690509edac --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenEndpoint.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server.jsr356.sockets; + +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +public class IdleTimeoutOnOpenEndpoint extends Endpoint implements MessageHandler.Whole +{ + private Session session; + + @Override + public void onOpen(Session session, EndpointConfig config) + { + this.session = session; + session.addMessageHandler(this); + session.setMaxIdleTimeout(500); + } + + @Override + public void onMessage(String message) + { + // echo message back (this is an indication of timeout failure) + session.getAsyncRemote().sendText(message); + } +} diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenSocket.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenSocket.java new file mode 100644 index 00000000000..3b08725edd3 --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/sockets/IdleTimeoutOnOpenSocket.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 org.eclipse.jetty.websocket.tests.server.jsr356.sockets; + +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint(value = "/idle-onopen-socket") +public class IdleTimeoutOnOpenSocket +{ + @OnOpen + public void onOpen(Session session) + { + session.setMaxIdleTimeout(500); + } + + @OnMessage + public String onMessage(String msg) + { + return msg; + } +} diff --git a/jetty-websocket/websocket-tests/src/test/resources/idle-timeout-config-web.xml b/jetty-websocket/websocket-tests/src/test/resources/idle-timeout-config-web.xml new file mode 100644 index 00000000000..9d8b2cb0b1e --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/resources/idle-timeout-config-web.xml @@ -0,0 +1,12 @@ + + + + + org.eclipse.jetty.websocket.tests.server.jsr356.IdleTimeoutContextListener + + \ No newline at end of file diff --git a/jetty-websocket/websocket-tests/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-tests/src/test/resources/jetty-logging.properties index 167871d3674..b35fe3d36e2 100644 --- a/jetty-websocket/websocket-tests/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-tests/src/test/resources/jetty-logging.properties @@ -22,14 +22,17 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.util.log.stderr.LONG=true +# org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG +org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=INFO -# org.eclipse.jetty.websocket.tests.LEVEL=DEBUG +org.eclipse.jetty.websocket.tests.LEVEL=DEBUG # org.eclipse.jetty.websocket.tests.client.LEVEL=DEBUG # org.eclipse.jetty.websocket.tests.client.jsr356.LEVEL=DEBUG # org.eclipse.jetty.websocket.tests.server.LEVEL=DEBUG # org.eclipse.jetty.websocket.tests.server.jsr356.LEVEL=DEBUG +org.eclipse.jetty.websocket.common.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.io.FrameFlusher.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG @@ -40,5 +43,6 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.websocket.common.message.LEVEL=DEBUG org.eclipse.jetty.websocket.common.CompletionCallback.LEVEL=ALL + ### Disabling intentional error out of RFCSocket org.eclipse.jetty.websocket.tests.server.RFCSocket.LEVEL=OFF