Issue #8170 - fix WebSocket close over HTTP/2

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2022-06-17 15:28:16 +10:00
parent 0699bc5326
commit e4b0db8666
2 changed files with 41 additions and 2 deletions

View File

@ -53,11 +53,11 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("idle timeout on {}: {}", this, failure); LOG.debug("idle timeout on {}: {}", this, failure);
offerFailure(failure);
boolean result = true; boolean result = true;
Connection connection = getConnection(); Connection connection = getConnection();
if (connection != null) if (connection != null)
result = connection.onIdleExpired(); result = connection.onIdleExpired();
offerFailure(failure);
consumer.accept(() -> close(failure)); consumer.accept(() -> close(failure));
return result; return result;
} }

View File

@ -18,6 +18,7 @@ import java.io.InterruptedIOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.URI; import java.net.URI;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.time.Duration;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -57,6 +58,7 @@ import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; import org.eclipse.jetty.websocket.api.exceptions.UpgradeException;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.core.server.internal.UpgradeHttpServletRequest; import org.eclipse.jetty.websocket.core.server.internal.UpgradeHttpServletRequest;
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
@ -68,6 +70,7 @@ import org.junit.jupiter.api.condition.OS;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -80,6 +83,7 @@ public class WebSocketOverHTTP2Test
private ServerConnector connector; private ServerConnector connector;
private ServerConnector tlsConnector; private ServerConnector tlsConnector;
private WebSocketClient wsClient; private WebSocketClient wsClient;
private ServletContextHandler context;
private void startServer() throws Exception private void startServer() throws Exception
{ {
@ -112,7 +116,7 @@ public class WebSocketOverHTTP2Test
tlsConnector = new ServerConnector(server, 1, 1, ssl, alpn, h1s, h2s); tlsConnector = new ServerConnector(server, 1, 1, ssl, alpn, h1s, h2s);
server.addConnector(tlsConnector); server.addConnector(tlsConnector);
ServletContextHandler context = new ServletContextHandler(server, "/"); context = new ServletContextHandler(server, "/");
context.addServlet(new ServletHolder(servlet), "/ws/*"); context.addServlet(new ServletHolder(servlet), "/ws/*");
JettyWebSocketServletContainerInitializer.configure(context, null); JettyWebSocketServletContainerInitializer.configure(context, null);
@ -337,6 +341,41 @@ public class WebSocketOverHTTP2Test
assertThat(cause, instanceOf(ClosedChannelException.class)); assertThat(cause, instanceOf(ClosedChannelException.class));
} }
@Test
public void testServerTimeout() throws Exception
{
startServer();
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(context.getServletContext());
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EchoSocket serverEndpoint = new EchoSocket();
container.addMapping("/specialEcho", (req, resp) -> serverEndpoint);
// Set up idle timeouts.
long timeout = 1000;
container.setIdleTimeout(Duration.ofMillis(timeout));
wsClient.setIdleTimeout(Duration.ZERO);
// Setup a websocket connection.
EventSocket clientEndpoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/specialEcho");
Session session = wsClient.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS);
session.getRemote().sendString("hello world");
String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
assertThat(received, equalTo("hello world"));
// Wait for timeout on server.
assertTrue(serverEndpoint.closeLatch.await(timeout * 2, TimeUnit.MILLISECONDS));
assertThat(serverEndpoint.closeCode, equalTo(StatusCode.SHUTDOWN));
assertThat(serverEndpoint.closeReason, containsStringIgnoringCase("timeout"));
assertNotNull(serverEndpoint.error);
// Wait for timeout on client.
assertTrue(clientEndpoint.closeLatch.await(timeout * 2, TimeUnit.MILLISECONDS));
assertThat(clientEndpoint.closeCode, equalTo(StatusCode.SHUTDOWN));
assertThat(clientEndpoint.closeReason, containsStringIgnoringCase("timeout"));
assertNull(clientEndpoint.error);
}
private static class TestJettyWebSocketServlet extends JettyWebSocketServlet private static class TestJettyWebSocketServlet extends JettyWebSocketServlet
{ {
@Override @Override