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

This commit is contained in:
Lachlan Roberts 2019-12-23 11:02:31 +11:00
commit db45b0b906
12 changed files with 344 additions and 81 deletions

View File

@ -58,6 +58,8 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore
protected SessionTableSchema _sessionTableSchema;
protected boolean _schemaProvided;
private static final ByteArrayInputStream EMPTY = new ByteArrayInputStream(new byte[0]);
/**
* SessionTableSchema
*/
@ -707,17 +709,23 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore
statement.setLong(10, data.getExpiry());
statement.setLong(11, data.getMaxInactiveMs());
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos))
if(!data.getAllAttributes().isEmpty())
{
SessionData.serializeAttributes(data, oos);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
statement.executeUpdate();
if (LOG.isDebugEnabled())
LOG.debug("Inserted session " + data);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos))
{
SessionData.serializeAttributes( data, oos );
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
statement.setBinaryStream( 12, bais, bytes.length );//attribute map as blob
}
}
else
{
statement.setBinaryStream( 12, EMPTY, 0);
}
statement.executeUpdate();
if ( LOG.isDebugEnabled() ) LOG.debug( "Inserted session " + data );
}
}
}
@ -737,20 +745,26 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore
statement.setLong(5, data.getExpiry());
statement.setLong(6, data.getMaxInactiveMs());
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos))
if(!data.getAllAttributes().isEmpty())
{
SessionData.serializeAttributes(data, oos);
byte[] bytes = baos.toByteArray();
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes))
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos))
{
statement.setBinaryStream(7, bais, bytes.length);//attribute map as blob
statement.executeUpdate();
if (LOG.isDebugEnabled())
LOG.debug("Updated session " + data);
SessionData.serializeAttributes(data, oos);
byte[] bytes = baos.toByteArray();
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes))
{
statement.setBinaryStream( 7, bais, bytes.length );//attribute map as blob
}
}
}
else
{
statement.setBinaryStream( 7, EMPTY, 0);
}
statement.executeUpdate();
if ( LOG.isDebugEnabled() ) LOG.debug( "Updated session " + data );
}
}
}

View File

@ -156,7 +156,7 @@ public class SessionData implements Serializable
LOG.info("Legacy serialization detected for {}", data.getId());
//legacy serialization was used, we have just deserialized the
//entire attribute map
data._attributes = new ConcurrentHashMap<String, Object>();
data._attributes = new ConcurrentHashMap<>();
data.putAllAttributes((Map<String, Object>)o);
}
}

View File

@ -29,6 +29,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Utf8Appendable;
import org.eclipse.jetty.util.component.Dumpable;
@ -76,6 +77,7 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
private long maxTextMessageSize = WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE;
private Duration idleTimeout = WebSocketConstants.DEFAULT_IDLE_TIMEOUT;
private Duration writeTimeout = WebSocketConstants.DEFAULT_WRITE_TIMEOUT;
private final ContextHandler contextHandler;
public WebSocketCoreSession(FrameHandler handler, Behavior behavior, Negotiated negotiated)
{
@ -83,9 +85,28 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
this.behavior = behavior;
this.negotiated = negotiated;
this.demanding = handler.isDemanding();
if (behavior == Behavior.SERVER)
{
ContextHandler.Context context = ContextHandler.getCurrentContext();
this.contextHandler = (context != null) ? context.getContextHandler() : null;
}
else
{
this.contextHandler = null;
}
negotiated.getExtensions().initialize(new IncomingAdaptor(), new OutgoingAdaptor(), this);
}
private void handle(Runnable runnable)
{
if (contextHandler != null)
contextHandler.handle(runnable);
else
runnable.run();
}
/**
* @return True if the sessions handling is demanding.
*/
@ -152,7 +173,6 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
throw new ProtocolException("Frame has non-transmittable status code");
}
}
}
}
@ -325,7 +345,7 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
{
try
{
handler.onClosed(closeStatus, callback);
handle(() -> handler.onClosed(closeStatus, callback));
}
catch (Throwable e)
{
@ -337,7 +357,7 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
Throwable cause = closeStatus.getCause();
try
{
handler.onError(cause, errorCallback);
handle(() -> handler.onError(cause, errorCallback));
}
catch (Throwable e)
{
@ -351,7 +371,7 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
{
try
{
handler.onClosed(closeStatus, callback);
handle(() -> handler.onClosed(closeStatus, callback));
}
catch (Throwable e)
{
@ -454,7 +474,7 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
try
{
// Open connection and handler
handler.onOpen(this, openCallback);
handle(() -> handler.onOpen(this, openCallback));
}
catch (Throwable t)
{
@ -664,7 +684,7 @@ public class WebSocketCoreSession implements IncomingFrames, FrameHandler.CoreSe
// Handle inbound frame
if (frame.getOpCode() != OpCode.CLOSE)
{
handler.onFrame(frame, callback);
handle(() -> handler.onFrame(frame, callback));
return;
}

View File

@ -292,6 +292,11 @@ public class JavaxWebSocketSession implements javax.websocket.Session
return frameHandler;
}
public void abort()
{
coreSession.abort();
}
/**
* {@inheritDoc}
*

View File

@ -72,7 +72,9 @@ public final class ContainerDefaultConfigurator extends Configurator
}
catch (Exception e)
{
throw new InstantiationException(String.format("%s: %s", e.getClass().getName(), e.getMessage()));
InstantiationException instantiationException = new InstantiationException();
instantiationException.initCause(e);
throw instantiationException;
}
}

View File

@ -39,6 +39,7 @@ public class JavaxWebSocketConfiguration extends AbstractConfiguration
addDependents("org.eclipse.jetty.annotations.AnnotationConfiguration", WebAppConfiguration.class.getName());
protectAndExpose("org.eclipse.jetty.websocket.servlet."); // For WebSocketUpgradeFilter
protectAndExpose("org.eclipse.jetty.websocket.javax.server.config.");
protectAndExpose("org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainerProvider");
hide("org.eclipse.jetty.websocket.javax.server.internal");
}
}

View File

@ -82,6 +82,15 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi
private boolean ssl = false;
private SslContextFactory.Server sslContextFactory;
public LocalServer()
{
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("qtp-LocalServer");
// Configure Server
server = new Server(threadPool);
}
public void enableSsl(boolean ssl)
{
this.ssl = ssl;
@ -179,11 +188,6 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi
@Override
protected void doStart() throws Exception
{
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("qtp-LocalServer");
// Configure Server
server = new Server(threadPool);
if (ssl)
{
// HTTP Configuration

View File

@ -24,12 +24,9 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.JAR;
@ -129,11 +126,7 @@ public class WSServer extends LocalServer implements LocalFuzzer.Provider
context.setContextPath(this.contextPath);
context.setBaseResource(new PathResource(this.contextDir));
context.setAttribute("org.eclipse.jetty.websocket.javax", Boolean.TRUE);
context.addConfiguration(new AnnotationConfiguration());
context.addConfiguration(new PlusConfiguration());
context.addConfiguration(new JavaxWebSocketConfiguration());
return context;
}
@ -162,7 +155,6 @@ public class WSServer extends LocalServer implements LocalFuzzer.Provider
@Override
protected Handler createRootHandler(Server server) throws Exception
{
HandlerCollection handlers = new HandlerCollection();
contexts = new ContextHandlerCollection();
return contexts;
}

View File

@ -0,0 +1,89 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.javax.tests.server;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.javax.tests.EventSocket;
import org.eclipse.jetty.websocket.javax.tests.WSServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static javax.websocket.CloseReason.CloseCodes.NORMAL_CLOSURE;
import static org.hamcrest.MatcherAssert.assertThat;
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 ContainerProviderServerTest
{
@ServerEndpoint("/echo")
public static class MySocket
{
@OnOpen
public void onOpen()
{
WebSocketContainer client = ContainerProvider.getWebSocketContainer();
assertNotNull(client);
}
}
private WSServer server;
@BeforeEach
public void startServer() throws Exception
{
Path testdir = MavenTestingUtils.getTargetTestingPath(ContainerProviderServerTest.class.getName());
server = new WSServer(testdir, "app");
server.createWebInf();
server.copyEndpoint(MySocket.class);
server.start();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
}
@AfterEach
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testJavaxWsContainerInServer() throws Exception
{
WebSocketContainer client = ContainerProvider.getWebSocketContainer();
EventSocket clientSocket = new EventSocket();
Session session = client.connectToServer(clientSocket, server.getWsUri().resolve("/app/echo"));
session.close(new CloseReason(NORMAL_CLOSURE, null));
assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(clientSocket.closeReason.getCloseCode(), is(NORMAL_CLOSURE));
assertNull(clientSocket.error);
}
}

View File

@ -0,0 +1,141 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.javax.tests.server;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSession;
import org.eclipse.jetty.websocket.javax.tests.EventSocket;
import org.eclipse.jetty.websocket.javax.tests.WSServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class WebAppClassLoaderTest
{
@ServerEndpoint("/echo")
public static class MySocket
{
public final static CountDownLatch closeLatch = new CountDownLatch(1);
public final static Map<String, ClassLoader> classLoaders = new ConcurrentHashMap<>();
public MySocket()
{
classLoaders.put("constructor", Thread.currentThread().getContextClassLoader());
}
@OnOpen
public void onOpen(Session session)
{
classLoaders.put("onOpen", Thread.currentThread().getContextClassLoader());
}
@OnMessage
public void onMessage(Session session, String msg)
{
classLoaders.put("onMessage", Thread.currentThread().getContextClassLoader());
}
@OnError
public void onError(Throwable error)
{
classLoaders.put("onError", Thread.currentThread().getContextClassLoader());
}
@OnClose
public void onClose(CloseReason closeReason)
{
classLoaders.put("onClose", Thread.currentThread().getContextClassLoader());
closeLatch.countDown();
}
}
private WSServer server;
private WebAppContext webapp;
@BeforeEach
public void startServer() throws Exception
{
Path testdir = MavenTestingUtils.getTargetTestingPath(WebAppClassLoaderTest.class.getName());
server = new WSServer(testdir, "app");
server.createWebInf();
server.copyEndpoint(MySocket.class);
server.start();
webapp = server.createWebAppContext();
server.deployWebapp(webapp);
}
@AfterEach
public void stopServer() throws Exception
{
server.stop();
}
private void awaitServerClose() throws Exception
{
ClassLoader webAppClassLoader = webapp.getClassLoader();
Class<?> mySocketClass = webAppClassLoader.loadClass(MySocket.class.getName());
CountDownLatch closeLatch = (CountDownLatch)mySocketClass.getDeclaredField("closeLatch").get(null);
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
private ClassLoader getClassLoader(String event) throws Exception
{
ClassLoader webAppClassLoader = webapp.getClassLoader();
Class<?> mySocketClass = webAppClassLoader.loadClass(MySocket.class.getName());
Map<String, ClassLoader> classLoaderMap = (Map)mySocketClass.getDeclaredField("classLoaders").get(null);
return classLoaderMap.get(event);
}
@ParameterizedTest
@ValueSource(strings = {"constructor", "onOpen", "onMessage", "onError", "onClose"})
public void testForWebAppClassLoader(String event) throws Exception
{
WebSocketContainer client = ContainerProvider.getWebSocketContainer();
EventSocket clientSocket = new EventSocket();
Session session = client.connectToServer(clientSocket, server.getWsUri().resolve("/app/echo"));
session.getBasicRemote().sendText("trigger onMessage -> onError -> onClose");
((JavaxWebSocketSession)session).abort();
assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS));
awaitServerClose();
ClassLoader webAppClassLoader = webapp.getClassLoader();
assertThat(event, getClassLoader(event), is(webAppClassLoader));
}
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
@ -263,39 +264,30 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
if (servletContext == null)
throw new IllegalStateException("null servletContext from request");
ClassLoader loader = servletContext.getClassLoader();
ClassLoader old = Thread.currentThread().getContextClassLoader();
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);
try
AtomicReference<Object> result = new AtomicReference<>();
((ContextHandler.Context)servletContext).getContextHandler().handle(() ->
result.set(creator.createWebSocket(upgradeRequest, upgradeResponse)));
Object websocketPojo = result.get();
// Handling for response forbidden (and similar paths)
if (upgradeResponse.isCommitted())
return null;
if (websocketPojo == null)
{
Thread.currentThread().setContextClassLoader(loader);
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);
Object websocketPojo = creator.createWebSocket(upgradeRequest, upgradeResponse);
// Handling for response forbidden (and similar paths)
if (upgradeResponse.isCommitted())
return null;
if (websocketPojo == null)
{
// no creation, sorry
upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
return null;
}
FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse);
if (frameHandler != null)
return frameHandler;
// no creation, sorry
upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
return null;
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse);
if (frameHandler != null)
return frameHandler;
return null;
}
@Override

View File

@ -173,17 +173,17 @@ public class JdbcTestHelper
ResultSet result = null;
try (Connection con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);)
{
statement = con.prepareStatement("select * from " + TABLE +
" where " + ID_COL + " = ? and " + CONTEXT_COL +
" = ? and virtualHost = ?");
statement = con.prepareStatement(
"select * from " + TABLE +
" where " + ID_COL + " = ? and " + CONTEXT_COL +
" = ? and virtualHost = ?" );
statement.setString(1, data.getId());
statement.setString(2, data.getContextPath());
statement.setString(3, data.getVhost());
result = statement.executeQuery();
if (!result.next())
return false;
if (!result.next()) return false;
assertEquals(data.getCreated(), result.getLong(CREATE_COL));
assertEquals(data.getAccessed(), result.getLong(ACCESS_COL));
@ -199,16 +199,19 @@ public class JdbcTestHelper
Blob blob = result.getBlob(MAP_COL);
SessionData tmp = new SessionData(data.getId(), data.getContextPath(), data.getVhost(), result.getLong(CREATE_COL),
result.getLong(ACCESS_COL), result.getLong(LAST_ACCESS_COL), result.getLong(MAX_IDLE_COL));
SessionData tmp =
new SessionData( data.getId(), data.getContextPath(), data.getVhost(), result.getLong(CREATE_COL),
result.getLong(ACCESS_COL), result.getLong(LAST_ACCESS_COL),
result.getLong(MAX_IDLE_COL));
try (InputStream is = blob.getBinaryStream();
ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
if (blob.length() > 0)
{
SessionData.deserializeAttributes(tmp, ois);
try (InputStream is = blob.getBinaryStream();
ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
{
SessionData.deserializeAttributes(tmp, ois);
}
}
//same number of attributes
assertEquals(data.getAllAttributes().size(), tmp.getAllAttributes().size());
//same keys