diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java index b522323da01..340421f4aba 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java @@ -20,8 +20,10 @@ package org.eclipse.jetty.websocket.tests.client; import java.io.IOException; import java.net.URI; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -36,6 +38,10 @@ import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.WebSocketSessionListener; +import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection; +import org.eclipse.jetty.websocket.server.WebSocketServerFactory; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint; @@ -43,16 +49,20 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for conditions due to bad networking. */ public class BadNetworkTest { + private static final Logger LOG = Log.getLogger(BadNetworkTest.class); private Server server; private WebSocketClient client; + private ServletContextHandler context; @BeforeEach public void startClient() throws Exception @@ -71,16 +81,17 @@ public class BadNetworkTest connector.setPort(0); server.addConnector(connector); - ServletContextHandler context = new ServletContextHandler(); + context = new ServletContextHandler(); context.setContextPath("/"); + ServletHolder holder = new ServletHolder(new WebSocketServlet() { @Override public void configure(WebSocketServletFactory factory) { - factory.getPolicy().setIdleTimeout(10000); + factory.getPolicy().setIdleTimeout(100000); factory.getPolicy().setMaxTextMessageSize(1024 * 1024 * 2); - factory.register(ServerEndpoint.class); + factory.setCreator((req, resp) -> new ServerEndpoint()); } }); context.addServlet(holder, "/ws"); @@ -108,6 +119,23 @@ public class BadNetworkTest @Test public void testAbruptClientClose() throws Exception { + AtomicReference serverSessionRef = new AtomicReference<>(); + CountDownLatch serverCloseLatch = new CountDownLatch(1); + WebSocketServerFactory wssf = (WebSocketServerFactory) context.getServletContext().getAttribute(WebSocketServletFactory.class.getName()); + wssf.addSessionListener(new WebSocketSessionListener() { + @Override + public void onSessionOpened(WebSocketSession session) + { + serverSessionRef.set(session); + } + + @Override + public void onSessionClosed(WebSocketSession session) + { + serverCloseLatch.countDown(); + } + }); + CloseTrackingEndpoint wsocket = new CloseTrackingEndpoint(); URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/ws")); @@ -116,14 +144,24 @@ public class BadNetworkTest // Validate that we are connected future.get(30,TimeUnit.SECONDS); + WebSocketSession serverSession = serverSessionRef.get(); + // Have client disconnect abruptly Session session = wsocket.getSession(); + LOG.info("client.disconnect"); session.disconnect(); // Client Socket should see a close event, with status NO_CLOSE // This event is automatically supplied by the underlying WebSocketClientConnection // in the situation of a bad network connection. wsocket.assertReceivedCloseEvent(5000, is(StatusCode.NO_CLOSE), containsString("")); + + TimeUnit.SECONDS.sleep(1); // Let server side close connection + + assertTrue(serverCloseLatch.await(1, TimeUnit.SECONDS), "Server Session Close should have happened"); + + AbstractWebSocketConnection conn = (AbstractWebSocketConnection) serverSession.getConnection(); + assertThat("Connection.isOpen", conn.isOpen(), is(false)); } @Test 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 0c8b54ff7a4..637cfd8ceff 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 @@ -446,6 +446,10 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp if (readMode == ReadMode.EOF) { readState.eof(); + + // Handle case where the remote connection was abruptly terminated without a close frame + CloseInfo close = new CloseInfo(StatusCode.SHUTDOWN); + close(close, new DisconnectCallback(this)); } else if (!readState.suspend()) {