Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Lachlan Roberts 2021-01-20 16:29:34 +11:00
commit 7de53ea445
10 changed files with 188 additions and 38 deletions

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.core.server.internal;
package org.eclipse.jetty.websocket.core.server;
import java.io.IOException;
@ -19,9 +19,14 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.core.Configuration;
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
{
static Handshaker newInstance()
{
return new HandshakerSelector();
}
boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException;
}

View File

@ -34,7 +34,6 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -124,6 +123,11 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
this.components = components;
}
public Handshaker getHandshaker()
{
return handshaker;
}
@Override
public void lifeCycleStopping(LifeCycle context)
{
@ -176,7 +180,7 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
*/
public void addMapping(PathSpec pathSpec, WebSocketCreator creator, FrameHandlerFactory factory, Configuration.Customizer customizer) throws WebSocketException
{
mappings.put(pathSpec, new CreatorNegotiator(creator, factory, customizer));
mappings.put(pathSpec, WebSocketNegotiator.from(creator, factory, customizer));
}
/**

View File

@ -18,11 +18,17 @@ import java.util.function.Function;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.internal.CreatorNegotiator;
public interface WebSocketNegotiator extends Configuration.Customizer
{
FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException;
@Override
default void customize(Configuration configurable)
{
}
static WebSocketNegotiator from(Function<WebSocketNegotiation, FrameHandler> negotiate)
{
return from(negotiate, null);
@ -40,6 +46,16 @@ public interface WebSocketNegotiator extends Configuration.Customizer
};
}
static WebSocketNegotiator from(WebSocketCreator creator, FrameHandlerFactory factory)
{
return from(creator, factory, null);
}
static WebSocketNegotiator from(WebSocketCreator creator, FrameHandlerFactory factory, Configuration.Customizer customizer)
{
return new CreatorNegotiator(creator, factory, customizer);
}
abstract class AbstractNegotiator extends Configuration.ConfigurationCustomizer implements WebSocketNegotiator
{
final Configuration.Customizer customizer;

View File

@ -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.WebSocketConnection;
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.WebSocketNegotiator;
import org.slf4j.Logger;

View File

@ -32,6 +32,11 @@ public class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
private final WebSocketCreator creator;
private final FrameHandlerFactory factory;
public CreatorNegotiator(WebSocketCreator creator, FrameHandlerFactory factory)
{
this(creator, factory, null);
}
public CreatorNegotiator(WebSocketCreator creator, FrameHandlerFactory factory, Customizer customizer)
{
super(customizer);

View File

@ -19,6 +19,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
/**

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.websocket.server;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
@ -21,6 +22,8 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ -32,10 +35,14 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WebSocketSessionListener;
import org.eclipse.jetty.websocket.common.SessionTracker;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
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.WebSocketMappings;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeRequest;
import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeResponse;
@ -69,7 +76,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
// Create the Jetty ServerContainer implementation
WebSocketMappings mappings = WebSocketMappings.ensureMappings(servletContext);
container = new JettyWebSocketServerContainer(contextHandler, mappings, executor);
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);
@ -82,7 +90,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
private final ServletContextHandler contextHandler;
private final WebSocketMappings webSocketMappings;
private final FrameHandlerFactory frameHandlerFactory;
private final WebSocketComponents components;
private final JettyServerFrameHandlerFactory frameHandlerFactory;
private final Executor executor;
private final Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer();
@ -95,21 +104,14 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
* @param webSocketMappings the {@link WebSocketMappings} that this container belongs to
* @param executor the {@link Executor} to use
*/
JettyWebSocketServerContainer(ServletContextHandler contextHandler, WebSocketMappings webSocketMappings, Executor executor)
JettyWebSocketServerContainer(ServletContextHandler contextHandler, WebSocketMappings webSocketMappings, WebSocketComponents components, Executor executor)
{
this.contextHandler = contextHandler;
this.webSocketMappings = webSocketMappings;
this.components = components;
this.executor = executor;
// Ensure there is a FrameHandlerFactory
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
if (factory == null)
{
factory = new JettyServerFrameHandlerFactory(this);
contextHandler.addManaged(factory);
contextHandler.addEventListener(factory);
}
frameHandlerFactory = factory;
this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this);
addBean(frameHandlerFactory);
addSessionListener(sessionTracker);
addBean(sessionTracker);
@ -145,6 +147,23 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
});
}
/**
* An immediate programmatic WebSocket upgrade that does not register a mapping or create a {@link WebSocketUpgradeFilter}.
* @param creator the WebSocketCreator to use.
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
* @return true if the connection was successfully upgraded to WebSocket.
* @throws IOException if an I/O error occurs.
*/
public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
{
WebSocketCreator coreCreator = (req, resp) -> creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp));
WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory, customizer);
Handshaker handshaker = webSocketMappings.getHandshaker();
return handshaker.upgradeRequest(negotiator, request, response, components, null);
}
@Override
public Executor getExecutor()
{

View File

@ -113,7 +113,6 @@ public abstract class JettyWebSocketServlet extends HttpServlet
private FrameHandlerFactory getFactory()
{
JettyServerFrameHandlerFactory frameHandlerFactory = JettyServerFrameHandlerFactory.getFactory(getServletContext());
if (frameHandlerFactory == null)
throw new IllegalStateException("JettyServerFrameHandlerFactory not found");

View File

@ -14,9 +14,6 @@
package org.eclipse.jetty.websocket.server.internal;
import jakarta.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.common.JettyWebSocketFrameHandler;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
@ -24,13 +21,14 @@ 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.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");
return contextHandler.getBean(JettyServerFrameHandlerFactory.class);
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext);
return (container == null) ? null : container.getBean(JettyServerFrameHandlerFactory.class);
}
public JettyServerFrameHandlerFactory(WebSocketContainer container)
@ -46,16 +44,4 @@ public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFa
frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
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);
}
}
}

View File

@ -0,0 +1,114 @@
//
// ========================================================================
// 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 jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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.server.JettyWebSocketCreator;
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 JettyWebSocketServerContainer container;
@Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
container = JettyWebSocketServerContainer.getContainer(getServletContext());
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
JettyWebSocketCreator creator = (req, resp) -> new EchoSocket();
container.upgrade(creator, request, response);
}
}
@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));
}
}