mirror of
https://github.com/jetty/jetty.project.git
synced 2025-03-04 04:49:12 +00:00
Remove WebSocketComponents & HouseKeeper on Server restart. (#6218)
* Remove WebSocketComponents & HouseKeeper on Server restart. * Add testing for cleanup of websocket when stopping server. * Add removeFilterHolder and removeFilterMapping methods on ServletHandler. Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
802d32d2a8
commit
cd73338b84
@ -339,6 +339,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
|
|||||||
_houseKeeper.stop();
|
_houseKeeper.stop();
|
||||||
if (_ownHouseKeeper)
|
if (_ownHouseKeeper)
|
||||||
{
|
{
|
||||||
|
removeBean(_houseKeeper);
|
||||||
_houseKeeper = null;
|
_houseKeeper = null;
|
||||||
}
|
}
|
||||||
_random = null;
|
_random = null;
|
||||||
|
@ -1195,6 +1195,34 @@ public class ServletHandler extends ScopedHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeFilterHolder(FilterHolder holder)
|
||||||
|
{
|
||||||
|
if (holder == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try (AutoLock ignored = lock())
|
||||||
|
{
|
||||||
|
FilterHolder[] holders = Arrays.stream(getFilters())
|
||||||
|
.filter(h -> h != holder)
|
||||||
|
.toArray(FilterHolder[]::new);
|
||||||
|
setFilters(holders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFilterMapping(FilterMapping mapping)
|
||||||
|
{
|
||||||
|
if (mapping == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try (AutoLock ignored = lock())
|
||||||
|
{
|
||||||
|
FilterMapping[] mappings = Arrays.stream(getFilterMappings())
|
||||||
|
.filter(m -> m != mapping)
|
||||||
|
.toArray(FilterMapping[]::new);
|
||||||
|
setFilterMappings(mappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void updateNameMappings()
|
protected void updateNameMappings()
|
||||||
{
|
{
|
||||||
try (AutoLock ignored = lock())
|
try (AutoLock ignored = lock())
|
||||||
|
@ -13,12 +13,12 @@
|
|||||||
|
|
||||||
package org.eclipse.jetty.websocket.core.server;
|
package org.eclipse.jetty.websocket.core.server;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.compression.DeflaterPool;
|
import org.eclipse.jetty.util.compression.DeflaterPool;
|
||||||
@ -93,17 +93,21 @@ public class WebSocketServerComponents extends WebSocketComponents
|
|||||||
if (server.contains(bufferPool))
|
if (server.contains(bufferPool))
|
||||||
serverComponents.unmanage(bufferPool);
|
serverComponents.unmanage(bufferPool);
|
||||||
|
|
||||||
servletContext.setAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE, serverComponents);
|
// Stop the WebSocketComponents when the ContextHandler stops.
|
||||||
LifeCycle.start(serverComponents);
|
ContextHandler contextHandler = Objects.requireNonNull(ContextHandler.getContextHandler(servletContext));
|
||||||
servletContext.addListener(new ServletContextListener()
|
contextHandler.addManaged(serverComponents);
|
||||||
|
contextHandler.addEventListener(new LifeCycle.Listener()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void contextDestroyed(ServletContextEvent sce)
|
public void lifeCycleStopping(LifeCycle event)
|
||||||
{
|
{
|
||||||
LifeCycle.stop(serverComponents);
|
servletContext.removeAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE);
|
||||||
|
contextHandler.removeBean(serverComponents);
|
||||||
|
contextHandler.removeEventListener(this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
servletContext.setAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE, serverComponents);
|
||||||
return serverComponents;
|
return serverComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,46 +59,60 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||||||
if (contextHandler.getServer() == null)
|
if (contextHandler.getServer() == null)
|
||||||
throw new IllegalStateException("Server has not been set on the ServletContextHandler");
|
throw new IllegalStateException("Server has not been set on the ServletContextHandler");
|
||||||
|
|
||||||
JavaxWebSocketServerContainer container = getContainer(servletContext);
|
JavaxWebSocketServerContainer containerFromServletContext = getContainer(servletContext);
|
||||||
if (container == null)
|
if (containerFromServletContext != null)
|
||||||
|
return containerFromServletContext;
|
||||||
|
|
||||||
|
Function<WebSocketComponents, WebSocketCoreClient> coreClientSupplier = (wsComponents) ->
|
||||||
{
|
{
|
||||||
Function<WebSocketComponents, WebSocketCoreClient> coreClientSupplier = (wsComponents) ->
|
WebSocketCoreClient coreClient = (WebSocketCoreClient)servletContext.getAttribute(WebSocketCoreClient.WEBSOCKET_CORECLIENT_ATTRIBUTE);
|
||||||
|
if (coreClient == null)
|
||||||
{
|
{
|
||||||
WebSocketCoreClient coreClient = (WebSocketCoreClient)servletContext.getAttribute(WebSocketCoreClient.WEBSOCKET_CORECLIENT_ATTRIBUTE);
|
// Find Pre-Existing (Shared?) HttpClient and/or executor
|
||||||
if (coreClient == null)
|
HttpClient httpClient = (HttpClient)servletContext.getAttribute(JavaxWebSocketServletContainerInitializer.HTTPCLIENT_ATTRIBUTE);
|
||||||
{
|
if (httpClient == null)
|
||||||
// Find Pre-Existing (Shared?) HttpClient and/or executor
|
httpClient = (HttpClient)contextHandler.getServer().getAttribute(JavaxWebSocketServletContainerInitializer.HTTPCLIENT_ATTRIBUTE);
|
||||||
HttpClient httpClient = (HttpClient)servletContext.getAttribute(JavaxWebSocketServletContainerInitializer.HTTPCLIENT_ATTRIBUTE);
|
|
||||||
if (httpClient == null)
|
|
||||||
httpClient = (HttpClient)contextHandler.getServer().getAttribute(JavaxWebSocketServletContainerInitializer.HTTPCLIENT_ATTRIBUTE);
|
|
||||||
|
|
||||||
Executor executor = httpClient == null ? null : httpClient.getExecutor();
|
Executor executor = httpClient == null ? null : httpClient.getExecutor();
|
||||||
if (executor == null)
|
if (executor == null)
|
||||||
executor = (Executor)servletContext.getAttribute("org.eclipse.jetty.server.Executor");
|
executor = (Executor)servletContext.getAttribute("org.eclipse.jetty.server.Executor");
|
||||||
if (executor == null)
|
if (executor == null)
|
||||||
executor = contextHandler.getServer().getThreadPool();
|
executor = contextHandler.getServer().getThreadPool();
|
||||||
|
|
||||||
if (httpClient != null && httpClient.getExecutor() == null)
|
if (httpClient != null && httpClient.getExecutor() == null)
|
||||||
httpClient.setExecutor(executor);
|
httpClient.setExecutor(executor);
|
||||||
|
|
||||||
// create the core client
|
// create the core client
|
||||||
coreClient = new WebSocketCoreClient(httpClient, wsComponents);
|
coreClient = new WebSocketCoreClient(httpClient, wsComponents);
|
||||||
coreClient.getHttpClient().setName("Javax-WebSocketClient@" + Integer.toHexString(coreClient.getHttpClient().hashCode()));
|
coreClient.getHttpClient().setName("Javax-WebSocketClient@" + Integer.toHexString(coreClient.getHttpClient().hashCode()));
|
||||||
if (executor != null && httpClient == null)
|
if (executor != null && httpClient == null)
|
||||||
coreClient.getHttpClient().setExecutor(executor);
|
coreClient.getHttpClient().setExecutor(executor);
|
||||||
servletContext.setAttribute(WebSocketCoreClient.WEBSOCKET_CORECLIENT_ATTRIBUTE, coreClient);
|
servletContext.setAttribute(WebSocketCoreClient.WEBSOCKET_CORECLIENT_ATTRIBUTE, coreClient);
|
||||||
}
|
}
|
||||||
return coreClient;
|
return coreClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create the Jetty ServerContainer implementation
|
||||||
|
JavaxWebSocketServerContainer container = new JavaxWebSocketServerContainer(
|
||||||
|
WebSocketMappings.ensureMappings(servletContext),
|
||||||
|
WebSocketServerComponents.getWebSocketComponents(servletContext),
|
||||||
|
coreClientSupplier);
|
||||||
|
|
||||||
|
// Manage the lifecycle of the Container.
|
||||||
|
contextHandler.addManaged(container);
|
||||||
|
contextHandler.addEventListener(container);
|
||||||
|
contextHandler.addEventListener(new LifeCycle.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void lifeCycleStopping(LifeCycle event)
|
||||||
|
{
|
||||||
|
servletContext.removeAttribute(JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE);
|
||||||
|
contextHandler.removeBean(container);
|
||||||
|
contextHandler.removeEventListener(container);
|
||||||
|
contextHandler.removeEventListener(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Create the Jetty ServerContainer implementation
|
|
||||||
container = new JavaxWebSocketServerContainer(
|
|
||||||
WebSocketMappings.ensureMappings(servletContext),
|
|
||||||
WebSocketServerComponents.getWebSocketComponents(servletContext),
|
|
||||||
coreClientSupplier);
|
|
||||||
contextHandler.addManaged(container);
|
|
||||||
contextHandler.addEventListener(container);
|
|
||||||
}
|
|
||||||
// Store a reference to the ServerContainer per - javax.websocket spec 1.0 final - section 6.4: Programmatic Server Deployment
|
// Store a reference to the ServerContainer per - javax.websocket spec 1.0 final - section 6.4: Programmatic Server Deployment
|
||||||
servletContext.setAttribute(JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE, container);
|
servletContext.setAttribute(JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE, container);
|
||||||
return container;
|
return container;
|
||||||
@ -140,18 +154,6 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||||||
this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this);
|
this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void lifeCycleStopping(LifeCycle context)
|
|
||||||
{
|
|
||||||
ContextHandler contextHandler = (ContextHandler)context;
|
|
||||||
JavaxWebSocketServerContainer container = contextHandler.getBean(JavaxWebSocketServerContainer.class);
|
|
||||||
if (container == this)
|
|
||||||
{
|
|
||||||
contextHandler.removeBean(container);
|
|
||||||
LifeCycle.stop(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaxWebSocketServerFrameHandlerFactory getFrameHandlerFactory()
|
public JavaxWebSocketServerFrameHandlerFactory getFrameHandlerFactory()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.websocket.javax.tests;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.websocket.Session;
|
||||||
|
|
||||||
|
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.websocket.core.server.WebSocketServerComponents;
|
||||||
|
import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer;
|
||||||
|
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
|
||||||
|
import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
|
||||||
|
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.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class JavaxWebSocketRestartTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector connector;
|
||||||
|
private JavaxWebSocketClientContainer client;
|
||||||
|
private ServletContextHandler contextHandler;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before() throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
contextHandler.setContextPath("/");
|
||||||
|
server.setHandler(contextHandler);
|
||||||
|
|
||||||
|
client = new JavaxWebSocketClientContainer();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void stop() throws Exception
|
||||||
|
{
|
||||||
|
client.stop();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWebSocketRestart() throws Exception
|
||||||
|
{
|
||||||
|
JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
|
||||||
|
container.addEndpoint(EchoSocket.class));
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
int numEventListeners = contextHandler.getEventListeners().size();
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
server.stop();
|
||||||
|
server.start();
|
||||||
|
testEchoMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have not accumulated websocket resources by restarting.
|
||||||
|
assertThat(contextHandler.getEventListeners().size(), is(numEventListeners));
|
||||||
|
assertThat(contextHandler.getContainedBeans(JavaxWebSocketServerContainer.class).size(), is(1));
|
||||||
|
assertThat(contextHandler.getContainedBeans(WebSocketServerComponents.class).size(), is(1));
|
||||||
|
assertNotNull(contextHandler.getServletContext().getAttribute(WebSocketServerComponents.WEBSOCKET_COMPONENTS_ATTRIBUTE));
|
||||||
|
assertNotNull(contextHandler.getServletContext().getAttribute(JavaxWebSocketServerContainer.JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE));
|
||||||
|
|
||||||
|
// We have one filter, and it is a WebSocketUpgradeFilter.
|
||||||
|
FilterHolder[] filters = contextHandler.getServletHandler().getFilters();
|
||||||
|
assertThat(filters.length, is(1));
|
||||||
|
assertThat(filters[0].getFilter(), instanceOf(WebSocketUpgradeFilter.class));
|
||||||
|
|
||||||
|
// After stopping the websocket resources are cleaned up.
|
||||||
|
server.stop();
|
||||||
|
assertThat(contextHandler.getEventListeners().size(), is(0));
|
||||||
|
assertThat(contextHandler.getContainedBeans(JavaxWebSocketServerContainer.class).size(), is(0));
|
||||||
|
assertThat(contextHandler.getContainedBeans(WebSocketServerComponents.class).size(), is(0));
|
||||||
|
assertNull(contextHandler.getServletContext().getAttribute(WebSocketServerComponents.WEBSOCKET_COMPONENTS_ATTRIBUTE));
|
||||||
|
assertNull(contextHandler.getServletContext().getAttribute(JavaxWebSocketServerContainer.JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE));
|
||||||
|
assertThat(contextHandler.getServletHandler().getFilters().length, is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testEchoMessage() throws Exception
|
||||||
|
{
|
||||||
|
// Test we can upgrade to websocket and send a message.
|
||||||
|
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
|
||||||
|
EventSocket socket = new EventSocket();
|
||||||
|
try (Session session = client.connectToServer(socket, uri))
|
||||||
|
{
|
||||||
|
session.getBasicRemote().sendText("hello world");
|
||||||
|
}
|
||||||
|
assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
String msg = socket.textMessages.poll();
|
||||||
|
assertThat(msg, is("hello world"));
|
||||||
|
}
|
||||||
|
}
|
@ -66,23 +66,37 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
|
|||||||
if (contextHandler.getServer() == null)
|
if (contextHandler.getServer() == null)
|
||||||
throw new IllegalStateException("Server has not been set on the ServletContextHandler");
|
throw new IllegalStateException("Server has not been set on the ServletContextHandler");
|
||||||
|
|
||||||
JettyWebSocketServerContainer container = getContainer(servletContext);
|
// If we find a container in the servlet context return it.
|
||||||
if (container == null)
|
JettyWebSocketServerContainer containerFromServletContext = getContainer(servletContext);
|
||||||
|
if (containerFromServletContext != null)
|
||||||
|
return containerFromServletContext;
|
||||||
|
|
||||||
|
// Find Pre-Existing executor.
|
||||||
|
Executor executor = (Executor)servletContext.getAttribute("org.eclipse.jetty.server.Executor");
|
||||||
|
if (executor == null)
|
||||||
|
executor = contextHandler.getServer().getThreadPool();
|
||||||
|
|
||||||
|
// Create the Jetty ServerContainer implementation.
|
||||||
|
WebSocketMappings mappings = WebSocketMappings.ensureMappings(servletContext);
|
||||||
|
WebSocketComponents components = WebSocketServerComponents.getWebSocketComponents(servletContext);
|
||||||
|
JettyWebSocketServerContainer container = new JettyWebSocketServerContainer(contextHandler, mappings, components, executor);
|
||||||
|
|
||||||
|
// Manage the lifecycle of the Container.
|
||||||
|
contextHandler.addManaged(container);
|
||||||
|
contextHandler.addEventListener(container);
|
||||||
|
contextHandler.addEventListener(new LifeCycle.Listener()
|
||||||
{
|
{
|
||||||
// Find Pre-Existing executor
|
@Override
|
||||||
Executor executor = (Executor)servletContext.getAttribute("org.eclipse.jetty.server.Executor");
|
public void lifeCycleStopping(LifeCycle event)
|
||||||
if (executor == null)
|
{
|
||||||
executor = contextHandler.getServer().getThreadPool();
|
contextHandler.getServletContext().removeAttribute(JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE);
|
||||||
|
contextHandler.removeBean(container);
|
||||||
// Create the Jetty ServerContainer implementation
|
contextHandler.removeEventListener(container);
|
||||||
WebSocketMappings mappings = WebSocketMappings.ensureMappings(servletContext);
|
contextHandler.removeEventListener(this);
|
||||||
WebSocketComponents components = WebSocketServerComponents.getWebSocketComponents(servletContext);
|
}
|
||||||
container = new JettyWebSocketServerContainer(contextHandler, mappings, components, executor);
|
});
|
||||||
servletContext.setAttribute(JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE, container);
|
|
||||||
contextHandler.addManaged(container);
|
|
||||||
contextHandler.addEventListener(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
servletContext.setAttribute(JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE, container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.websocket.tests;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
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.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
|
||||||
|
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
|
||||||
|
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
|
||||||
|
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.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class JettyWebSocketRestartTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector connector;
|
||||||
|
private WebSocketClient client;
|
||||||
|
private ServletContextHandler contextHandler;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before() throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
contextHandler.setContextPath("/");
|
||||||
|
server.setHandler(contextHandler);
|
||||||
|
|
||||||
|
client = new WebSocketClient();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void stop() throws Exception
|
||||||
|
{
|
||||||
|
client.stop();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWebSocketRestart() throws Exception
|
||||||
|
{
|
||||||
|
JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
|
||||||
|
container.addMapping("/", EchoSocket.class));
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
int numEventListeners = contextHandler.getEventListeners().size();
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
server.stop();
|
||||||
|
server.start();
|
||||||
|
testEchoMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have not accumulated websocket resources by restarting.
|
||||||
|
assertThat(contextHandler.getEventListeners().size(), is(numEventListeners));
|
||||||
|
assertThat(contextHandler.getContainedBeans(JettyWebSocketServerContainer.class).size(), is(1));
|
||||||
|
assertThat(contextHandler.getContainedBeans(WebSocketServerComponents.class).size(), is(1));
|
||||||
|
assertNotNull(contextHandler.getServletContext().getAttribute(WebSocketServerComponents.WEBSOCKET_COMPONENTS_ATTRIBUTE));
|
||||||
|
assertNotNull(contextHandler.getServletContext().getAttribute(JettyWebSocketServerContainer.JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE));
|
||||||
|
|
||||||
|
// We have one filter, and it is a WebSocketUpgradeFilter.
|
||||||
|
FilterHolder[] filters = contextHandler.getServletHandler().getFilters();
|
||||||
|
assertThat(filters.length, is(1));
|
||||||
|
assertThat(filters[0].getFilter(), instanceOf(WebSocketUpgradeFilter.class));
|
||||||
|
|
||||||
|
// After stopping the websocket resources are cleaned up.
|
||||||
|
server.stop();
|
||||||
|
assertThat(contextHandler.getEventListeners().size(), is(0));
|
||||||
|
assertThat(contextHandler.getContainedBeans(JettyWebSocketServerContainer.class).size(), is(0));
|
||||||
|
assertThat(contextHandler.getContainedBeans(WebSocketServerComponents.class).size(), is(0));
|
||||||
|
assertNull(contextHandler.getServletContext().getAttribute(WebSocketServerComponents.WEBSOCKET_COMPONENTS_ATTRIBUTE));
|
||||||
|
assertNull(contextHandler.getServletContext().getAttribute(JettyWebSocketServerContainer.JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE));
|
||||||
|
assertThat(contextHandler.getServletHandler().getFilters().length, is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testEchoMessage() throws Exception
|
||||||
|
{
|
||||||
|
// Test we can upgrade to websocket and send a message.
|
||||||
|
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
|
||||||
|
EventSocket socket = new EventSocket();
|
||||||
|
CompletableFuture<Session> connect = client.connect(socket, uri);
|
||||||
|
try (Session session = connect.get(5, TimeUnit.SECONDS))
|
||||||
|
{
|
||||||
|
session.getRemote().sendString("hello world");
|
||||||
|
}
|
||||||
|
assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
String msg = socket.textMessages.poll();
|
||||||
|
assertThat(msg, is("hello world"));
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ import org.eclipse.jetty.servlet.FilterMapping;
|
|||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
import org.eclipse.jetty.util.component.Dumpable;
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.thread.AutoLock;
|
import org.eclipse.jetty.util.thread.AutoLock;
|
||||||
import org.eclipse.jetty.websocket.core.Configuration;
|
import org.eclipse.jetty.websocket.core.Configuration;
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
|
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
|
||||||
@ -123,6 +124,18 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
|
|||||||
servletHandler.prependFilter(holder);
|
servletHandler.prependFilter(holder);
|
||||||
servletHandler.prependFilterMapping(mapping);
|
servletHandler.prependFilterMapping(mapping);
|
||||||
|
|
||||||
|
// If we create the filter we must also make sure it is removed if the context is stopped.
|
||||||
|
contextHandler.addEventListener(new LifeCycle.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void lifeCycleStopping(LifeCycle event)
|
||||||
|
{
|
||||||
|
servletHandler.removeFilterHolder(holder);
|
||||||
|
servletHandler.removeFilterMapping(mapping);
|
||||||
|
contextHandler.removeEventListener(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
|
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
|
||||||
return holder;
|
return holder;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user