Issue #3422 - WebSocket wss CLOSE_WAIT on aborted client connection

+ Adding testcase to replicate
+ Fixing CLOSE_WAIT by issuing wsclose + disconnect on eof

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Joakim Erdfelt 2019-03-15 11:50:41 -05:00
parent 3b7338888b
commit 76de1c0f24
2 changed files with 45 additions and 3 deletions

View File

@ -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<WebSocketSession> 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

View File

@ -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())
{