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

This commit is contained in:
Lachlan Roberts 2020-08-19 08:33:48 +10:00
commit 931e10458a
5 changed files with 178 additions and 45 deletions

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 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.io;
import java.util.AbstractSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Predicate;
import org.eclipse.jetty.util.IncludeExcludeSet;
public class IncludeExcludeConnectionStatistics extends ConnectionStatistics
{
private final IncludeExcludeSet<Class<? extends Connection>, Connection> _set = new IncludeExcludeSet<>(ConnectionSet.class);
public void include(String className) throws ClassNotFoundException
{
_set.include(connectionForName(className));
}
public void include(Class<? extends Connection> clazz)
{
_set.include(clazz);
}
public void exclude(String className) throws ClassNotFoundException
{
_set.exclude(connectionForName(className));
}
public void exclude(Class<? extends Connection> clazz)
{
_set.exclude(clazz);
}
private Class<? extends Connection> connectionForName(String className) throws ClassNotFoundException
{
Class<?> aClass = Class.forName(className);
if (!Connection.class.isAssignableFrom(aClass))
throw new IllegalArgumentException("Class is not a Connection");
@SuppressWarnings("unchecked")
Class<? extends Connection> connectionClass = (Class<? extends Connection>)aClass;
return connectionClass;
}
@Override
public void onOpened(Connection connection)
{
if (_set.test(connection))
super.onOpened(connection);
}
@Override
public void onClosed(Connection connection)
{
if (_set.test(connection))
super.onClosed(connection);
}
public static class ConnectionSet extends AbstractSet<Class<? extends Connection>> implements Predicate<Connection>
{
private final Set<Class<? extends Connection>> set = new HashSet<>();
@Override
public boolean add(Class<? extends Connection> aClass)
{
return set.add(aClass);
}
@Override
public boolean remove(Object o)
{
return set.remove(o);
}
@Override
public Iterator<Class<? extends Connection>> iterator()
{
return set.iterator();
}
@Override
public int size()
{
return set.size();
}
@Override
public boolean test(Connection connection)
{
if (connection == null)
return false;
return set.stream().anyMatch(c -> c.isAssignableFrom(connection.getClass()));
}
}
}

View File

@ -18,19 +18,20 @@
package org.eclipse.jetty.websocket.javax.common;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.CloseReason;
import javax.websocket.Session;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.Graceful;
public class SessionTracker extends AbstractLifeCycle implements JavaxWebSocketSessionListener, Graceful
public class SessionTracker extends AbstractLifeCycle implements JavaxWebSocketSessionListener, Graceful, Dumpable
{
private final Set<JavaxWebSocketSession> sessions = Collections.newSetFromMap(new ConcurrentHashMap<>());
private boolean isShutdown = false;
@ -88,4 +89,16 @@ public class SessionTracker extends AbstractLifeCycle implements JavaxWebSocketS
{
return isShutdown;
}
@ManagedAttribute("Total number of active WebSocket Sessions")
public int getNumSessions()
{
return sessions.size();
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this, sessions);
}
}

View File

@ -18,22 +18,22 @@
package org.eclipse.jetty.websocket.common;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketSessionListener;
public class SessionTracker extends AbstractLifeCycle implements WebSocketSessionListener, Graceful
public class SessionTracker extends AbstractLifeCycle implements WebSocketSessionListener, Graceful, Dumpable
{
private final Set<Session> sessions = Collections.newSetFromMap(new ConcurrentHashMap<>());
private boolean isShutdown = false;
@ -91,4 +91,16 @@ public class SessionTracker extends AbstractLifeCycle implements WebSocketSessio
{
return isShutdown;
}
@ManagedAttribute("Total number of active WebSocket Sessions")
public int getNumSessions()
{
return sessions.size();
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this, sessions);
}
}

View File

@ -73,6 +73,12 @@
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.tests;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
@ -25,8 +26,8 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.io.IncludeExcludeConnectionStatistics;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
@ -38,11 +39,10 @@ import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.internal.Generator;
import org.eclipse.jetty.websocket.core.internal.WebSocketConnection;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
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.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -51,53 +51,47 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class WebSocketStatsTest
{
public static class MyWebSocketServlet extends JettyWebSocketServlet
{
@Override
public void configure(JettyWebSocketServletFactory factory)
{
factory.setAutoFragment(false);
factory.addMapping("/", (req, resp) -> new EchoSocket());
}
}
private final CountDownLatch wsConnectionClosed = new CountDownLatch(1);
private Server server;
private ServerConnector connector;
private WebSocketClient client;
private ConnectionStatistics statistics;
private final CountDownLatch wsUpgradeComplete = new CountDownLatch(1);
private final CountDownLatch wsConnectionClosed = new CountDownLatch(1);
private IncludeExcludeConnectionStatistics statistics;
@BeforeEach
public void start() throws Exception
{
statistics = new ConnectionStatistics()
statistics = new IncludeExcludeConnectionStatistics();
statistics.include(WebSocketConnection.class);
Connection.Listener.Adapter wsCloseListener = new Connection.Listener.Adapter()
{
@Override
public void onClosed(Connection connection)
{
super.onClosed(connection);
if (connection instanceof WebSocketConnection)
wsConnectionClosed.countDown();
else if (connection instanceof HttpConnection)
wsUpgradeComplete.countDown();
}
};
server = new Server();
connector = new ServerConnector(server);
connector.addBean(statistics);
connector.addBean(wsCloseListener);
server.addConnector(connector);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
contextHandler.addServlet(MyWebSocketServlet.class, "/testPath");
JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
container.addMapping("/", EchoSocket.class));
server.setHandler(contextHandler);
JettyWebSocketServletContainerInitializer.configure(contextHandler, null);
client = new WebSocketClient();
// Setup JMX.
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);
server.start();
client.start();
}
@ -105,8 +99,8 @@ public class WebSocketStatsTest
@AfterEach
public void stop() throws Exception
{
client.stop();
server.stop();
client.stop();
}
long getFrameByteSize(Frame frame)
@ -117,25 +111,19 @@ public class WebSocketStatsTest
return headerBuffer.remaining() + frame.getPayloadLength();
}
@Disabled("off by 2 some of the time for large num of messages")
@Test
public void echoStatsTest() throws Exception
{
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/testPath");
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/");
EventSocket socket = new EventSocket();
CompletableFuture<Session> connect = client.connect(socket, uri);
final long numMessages = 1000;
final String msgText = "hello world";
long upgradeSentBytes;
long upgradeReceivedBytes;
try (Session session = connect.get(5, TimeUnit.SECONDS))
{
wsUpgradeComplete.await(5, TimeUnit.SECONDS);
upgradeSentBytes = statistics.getSentBytes();
upgradeReceivedBytes = statistics.getReceivedBytes();
assertThat(statistics.getConnections(), is(1L));
for (int i = 0; i < numMessages; i++)
{
session.getRemote().sendString(msgText);
@ -147,18 +135,18 @@ public class WebSocketStatsTest
assertThat(statistics.getConnectionsMax(), is(1L));
assertThat(statistics.getConnections(), is(0L));
assertThat(statistics.getSentMessages(), is(numMessages + 2L));
assertThat(statistics.getReceivedMessages(), is(numMessages + 2L));
// Sent and r eceived all of the echo messages + 1 for the close frame.
assertThat(statistics.getSentMessages(), is(numMessages + 1L));
assertThat(statistics.getReceivedMessages(), is(numMessages + 1L));
Frame textFrame = new Frame(OpCode.TEXT, msgText);
Frame closeFrame = CloseStatus.NORMAL_STATUS.toFrame();
final long textFrameSize = getFrameByteSize(textFrame);
final long closeFrameSize = getFrameByteSize(closeFrame);
final int maskSize = 4; // We use 4 byte mask for client frames in WSConnection
final long expectedSent = upgradeSentBytes + numMessages * textFrameSize + closeFrameSize;
final long expectedReceived = upgradeReceivedBytes + numMessages * (textFrameSize + maskSize) + closeFrameSize + maskSize;
final long expectedSent = numMessages * textFrameSize + closeFrameSize;
final long expectedReceived = numMessages * (textFrameSize + maskSize) + (closeFrameSize + maskSize);
assertThat(statistics.getSentBytes(), is(expectedSent));
assertThat(statistics.getReceivedBytes(), is(expectedReceived));