Issue #5866 - allow programmatic upgrades with websocket-core
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
7dfb44f7ad
commit
0a944ac0a9
|
@ -11,7 +11,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.core.server.internal;
|
package org.eclipse.jetty.websocket.core.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
@ -20,18 +20,17 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||||
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketCreator;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
|
||||||
|
|
||||||
public class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
|
public class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
|
||||||
{
|
{
|
||||||
private final WebSocketCreator creator;
|
private final WebSocketCreator creator;
|
||||||
private final FrameHandlerFactory factory;
|
private final FrameHandlerFactory factory;
|
||||||
|
|
||||||
|
public CreatorNegotiator(WebSocketCreator creator, FrameHandlerFactory factory)
|
||||||
|
{
|
||||||
|
this(creator, factory, null);
|
||||||
|
}
|
||||||
|
|
||||||
public CreatorNegotiator(WebSocketCreator creator, FrameHandlerFactory factory, Customizer customizer)
|
public CreatorNegotiator(WebSocketCreator creator, FrameHandlerFactory factory, Customizer customizer)
|
||||||
{
|
{
|
||||||
super(customizer);
|
super(customizer);
|
|
@ -11,7 +11,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.core.server.internal;
|
package org.eclipse.jetty.websocket.core.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -19,9 +19,14 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.core.Configuration;
|
import org.eclipse.jetty.websocket.core.Configuration;
|
||||||
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
import org.eclipse.jetty.websocket.core.server.internal.HandshakerSelector;
|
||||||
|
|
||||||
public interface Handshaker
|
public interface Handshaker
|
||||||
{
|
{
|
||||||
|
static Handshaker newInstance()
|
||||||
|
{
|
||||||
|
return new HandshakerSelector();
|
||||||
|
}
|
||||||
|
|
||||||
boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException;
|
boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException;
|
||||||
}
|
}
|
|
@ -33,8 +33,6 @@ import org.eclipse.jetty.websocket.core.CoreSession;
|
||||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||||
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
||||||
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
|
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
|
||||||
import org.eclipse.jetty.websocket.core.server.internal.CreatorNegotiator;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.internal.Handshaker;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.internal.HandshakerSelector;
|
import org.eclipse.jetty.websocket.core.server.internal.HandshakerSelector;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -54,14 +52,14 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(WebSocketMappings.class);
|
private static final Logger LOG = LoggerFactory.getLogger(WebSocketMappings.class);
|
||||||
public static final String WEBSOCKET_MAPPING_ATTRIBUTE = WebSocketMappings.class.getName();
|
public static final String WEBSOCKET_MAPPING_ATTRIBUTE = WebSocketMappings.class.getName();
|
||||||
|
|
||||||
public static WebSocketMappings getWebSocketNegotiator(ServletContext servletContext)
|
public static WebSocketMappings getMappings(ServletContext servletContext)
|
||||||
{
|
{
|
||||||
return (WebSocketMappings)servletContext.getAttribute(WEBSOCKET_MAPPING_ATTRIBUTE);
|
return (WebSocketMappings)servletContext.getAttribute(WEBSOCKET_MAPPING_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebSocketMappings ensureMappings(ServletContext servletContext)
|
public static WebSocketMappings ensureMappings(ServletContext servletContext)
|
||||||
{
|
{
|
||||||
WebSocketMappings mapping = getWebSocketNegotiator(servletContext);
|
WebSocketMappings mapping = getMappings(servletContext);
|
||||||
if (mapping == null)
|
if (mapping == null)
|
||||||
{
|
{
|
||||||
mapping = new WebSocketMappings(WebSocketServerComponents.getWebSocketComponents(servletContext));
|
mapping = new WebSocketMappings(WebSocketServerComponents.getWebSocketComponents(servletContext));
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.eclipse.jetty.websocket.core.internal.ExtensionStack;
|
||||||
import org.eclipse.jetty.websocket.core.internal.Negotiated;
|
import org.eclipse.jetty.websocket.core.internal.Negotiated;
|
||||||
import org.eclipse.jetty.websocket.core.internal.WebSocketConnection;
|
import org.eclipse.jetty.websocket.core.internal.WebSocketConnection;
|
||||||
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
|
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.Handshaker;
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
|
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.core.Configuration;
|
import org.eclipse.jetty.websocket.core.Configuration;
|
||||||
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.Handshaker;
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,7 +34,6 @@ import org.eclipse.jetty.websocket.common.SessionTracker;
|
||||||
import org.eclipse.jetty.websocket.core.Configuration;
|
import org.eclipse.jetty.websocket.core.Configuration;
|
||||||
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
|
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
|
||||||
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
|
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
|
||||||
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
|
|
||||||
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
|
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
|
||||||
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
|
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
|
||||||
import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeRequest;
|
import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeRequest;
|
||||||
|
@ -82,7 +81,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
|
||||||
|
|
||||||
private final ServletContextHandler contextHandler;
|
private final ServletContextHandler contextHandler;
|
||||||
private final WebSocketMappings webSocketMappings;
|
private final WebSocketMappings webSocketMappings;
|
||||||
private final FrameHandlerFactory frameHandlerFactory;
|
private final JettyServerFrameHandlerFactory frameHandlerFactory;
|
||||||
private final Executor executor;
|
private final Executor executor;
|
||||||
private final Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer();
|
private final Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer();
|
||||||
|
|
||||||
|
@ -100,16 +99,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
|
||||||
this.contextHandler = contextHandler;
|
this.contextHandler = contextHandler;
|
||||||
this.webSocketMappings = webSocketMappings;
|
this.webSocketMappings = webSocketMappings;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this);
|
||||||
// Ensure there is a FrameHandlerFactory
|
addBean(frameHandlerFactory);
|
||||||
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
|
|
||||||
if (factory == null)
|
|
||||||
{
|
|
||||||
factory = new JettyServerFrameHandlerFactory(this);
|
|
||||||
contextHandler.addManaged(factory);
|
|
||||||
contextHandler.addEventListener(factory);
|
|
||||||
}
|
|
||||||
frameHandlerFactory = factory;
|
|
||||||
|
|
||||||
addSessionListener(sessionTracker);
|
addSessionListener(sessionTracker);
|
||||||
addBean(sessionTracker);
|
addBean(sessionTracker);
|
||||||
|
|
|
@ -113,7 +113,6 @@ public abstract class JettyWebSocketServlet extends HttpServlet
|
||||||
private FrameHandlerFactory getFactory()
|
private FrameHandlerFactory getFactory()
|
||||||
{
|
{
|
||||||
JettyServerFrameHandlerFactory frameHandlerFactory = JettyServerFrameHandlerFactory.getFactory(getServletContext());
|
JettyServerFrameHandlerFactory frameHandlerFactory = JettyServerFrameHandlerFactory.getFactory(getServletContext());
|
||||||
|
|
||||||
if (frameHandlerFactory == null)
|
if (frameHandlerFactory == null)
|
||||||
throw new IllegalStateException("JettyServerFrameHandlerFactory not found");
|
throw new IllegalStateException("JettyServerFrameHandlerFactory not found");
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,6 @@ package org.eclipse.jetty.websocket.server.internal;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketContainer;
|
import org.eclipse.jetty.websocket.api.WebSocketContainer;
|
||||||
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
|
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
|
||||||
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
|
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
|
||||||
|
@ -25,13 +22,14 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||||
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
|
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
|
||||||
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
|
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
|
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
|
||||||
|
|
||||||
public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFactory implements FrameHandlerFactory, LifeCycle.Listener
|
public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFactory implements FrameHandlerFactory
|
||||||
{
|
{
|
||||||
public static JettyServerFrameHandlerFactory getFactory(ServletContext context)
|
public static JettyServerFrameHandlerFactory getFactory(ServletContext servletContext)
|
||||||
{
|
{
|
||||||
ServletContextHandler contextHandler = ServletContextHandler.getServletContextHandler(context, "JettyServerFrameHandlerFactory");
|
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext);
|
||||||
return contextHandler.getBean(JettyServerFrameHandlerFactory.class);
|
return (container == null) ? null : container.getBean(JettyServerFrameHandlerFactory.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JettyServerFrameHandlerFactory(WebSocketContainer container)
|
public JettyServerFrameHandlerFactory(WebSocketContainer container)
|
||||||
|
@ -47,16 +45,4 @@ public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFa
|
||||||
frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
|
frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
|
||||||
return frameHandler;
|
return frameHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void lifeCycleStopping(LifeCycle context)
|
|
||||||
{
|
|
||||||
ContextHandler contextHandler = (ContextHandler)context;
|
|
||||||
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
|
|
||||||
if (factory != null)
|
|
||||||
{
|
|
||||||
contextHandler.removeBean(factory);
|
|
||||||
LifeCycle.stop(factory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2020 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.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.CreatorNegotiator;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.Handshaker;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.WebSocketCreator;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||||
|
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.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.is;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class ProgrammaticWebSocketUpgradeTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector connector;
|
||||||
|
private WebSocketClient client;
|
||||||
|
private ServletContextHandler contextHandler;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before() throws Exception
|
||||||
|
{
|
||||||
|
client = new WebSocketClient();
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
contextHandler.setContextPath("/");
|
||||||
|
contextHandler.addServlet(new ServletHolder(new CustomUpgradeServlet()), "/");
|
||||||
|
server.setHandler(contextHandler);
|
||||||
|
|
||||||
|
JettyWebSocketServletContainerInitializer.configure(contextHandler, null);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void stop() throws Exception
|
||||||
|
{
|
||||||
|
client.stop();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CustomUpgradeServlet extends HttpServlet
|
||||||
|
{
|
||||||
|
private final Handshaker handshaker = Handshaker.newInstance();
|
||||||
|
private FrameHandlerFactory frameHandlerFactory;
|
||||||
|
private WebSocketComponents components;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ServletConfig config) throws ServletException
|
||||||
|
{
|
||||||
|
super.init(config);
|
||||||
|
ServletContext servletContext = getServletContext();
|
||||||
|
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext);
|
||||||
|
components = WebSocketServerComponents.getWebSocketComponents(servletContext);
|
||||||
|
frameHandlerFactory = container.getBean(FrameHandlerFactory.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
WebSocketCreator creator = (req, resp) -> new EchoSocket();
|
||||||
|
WebSocketNegotiator negotiator = new CreatorNegotiator(creator, frameHandlerFactory);
|
||||||
|
handshaker.upgradeRequest(negotiator, request, response, components, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProgrammaticWebSocketUpgrade() throws Exception
|
||||||
|
{
|
||||||
|
// Test we can upgrade to websocket and send a message.
|
||||||
|
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/path");
|
||||||
|
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"));
|
||||||
|
|
||||||
|
// WebSocketUpgradeFilter should not have been added.
|
||||||
|
assertThat(contextHandler.getServletHandler().getFilters().length, is(0));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue