runArgs = new ArrayList<>();
+ runArgs.add("--create-files");
+ UsageException usage = assertThrows(UsageException.class, () ->
+ {
+ ExecResults results = exec(runArgs, true);
+ if (results.exception != null)
+ {
+ throw results.exception;
+ }
+ });
+ assertThat(usage.getMessage(), containsString("Unknown modules=[does-not-exist, also-not-present]"));
+ }
+
@Test
public void testProvidersUsingDefault() throws Exception
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index d717f12a502..8033d6cd035 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -243,9 +243,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
{
// Fill the job queue with noop jobs to wakeup idle threads.
for (int i = 0; i < threads; ++i)
- {
- jobs.offer(NOOP);
- }
+ if (!jobs.offer(NOOP))
+ break;
// try to let jobs complete naturally for half our stop time
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
@@ -255,6 +254,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
// interrupt remaining threads
for (Thread thread : _threads)
{
+ if (thread == Thread.currentThread())
+ continue;
if (LOG.isDebugEnabled())
LOG.debug("Interrupting {}", thread);
thread.interrupt();
@@ -264,24 +265,21 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
Thread.yield();
- if (LOG.isDebugEnabled())
+
+ for (Thread unstopped : _threads)
{
- for (Thread unstopped : _threads)
+ if (unstopped == Thread.currentThread())
+ continue;
+ String stack = "";
+ if (LOG.isDebugEnabled())
{
StringBuilder dmp = new StringBuilder();
for (StackTraceElement element : unstopped.getStackTrace())
- {
dmp.append(System.lineSeparator()).append("\tat ").append(element);
- }
- LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
- }
- }
- else
- {
- for (Thread unstopped : _threads)
- {
- LOG.warn("{} Couldn't stop {}", this, unstopped);
+ stack = dmp.toString();
}
+
+ LOG.warn("Couldn't stop {}{}", unstopped, stack);
}
}
@@ -315,13 +313,32 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
private void joinThreads(long stopByNanos) throws InterruptedException
{
- for (Thread thread : _threads)
+ loop : while (true)
{
- long canWait = TimeUnit.NANOSECONDS.toMillis(stopByNanos - System.nanoTime());
- if (LOG.isDebugEnabled())
- LOG.debug("Waiting for {} for {}", thread, canWait);
- if (canWait > 0)
- thread.join(canWait);
+ for (Thread thread : _threads)
+ {
+ // Don't join ourselves
+ if (thread == Thread.currentThread())
+ continue;
+
+ long canWait = TimeUnit.NANOSECONDS.toMillis(stopByNanos - System.nanoTime());
+ if (LOG.isDebugEnabled())
+ LOG.debug("Waiting for {} for {}", thread, canWait);
+ if (canWait <= 0)
+ return;
+
+ try
+ {
+ thread.join(canWait);
+ }
+ catch (InterruptedException e)
+ {
+ // Don't stop waiting for a join if interrupted
+ continue loop;
+ }
+ }
+
+ return;
}
}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
index a8199035bbc..fa58df21680 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
@@ -101,6 +101,43 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
}
}
+ private static class StoppingTask implements Runnable
+ {
+ private final CountDownLatch _running;
+ private final CountDownLatch _blocked;
+ private final QueuedThreadPool _tp;
+ Thread _thread;
+ CountDownLatch _completed = new CountDownLatch(1);
+
+ public StoppingTask(CountDownLatch running, CountDownLatch blocked, QueuedThreadPool tp)
+ {
+ _running = running;
+ _blocked = blocked;
+ _tp = tp;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ _thread = Thread.currentThread();
+ _running.countDown();
+ _blocked.await();
+ _tp.doStop();
+ _completed.countDown();
+ }
+ catch (InterruptedException x)
+ {
+ x.printStackTrace();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
private class RunningJob implements Runnable
{
final CountDownLatch _run = new CountDownLatch(1);
@@ -947,6 +984,49 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
}
}
+ @Test
+ public void testInterruptedStop() throws Exception
+ {
+ QueuedThreadPool tp = new QueuedThreadPool();
+ tp.setStopTimeout(1000);
+ tp.start();
+
+ CountDownLatch running = new CountDownLatch(3);
+ CountDownLatch blocked = new CountDownLatch(1);
+ CountDownLatch forever = new CountDownLatch(2);
+ CountDownLatch interrupted = new CountDownLatch(1);
+
+ Runnable runForever = () ->
+ {
+ try
+ {
+ running.countDown();
+ forever.await();
+ }
+ catch (InterruptedException x)
+ {
+ interrupted.countDown();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ };
+
+ StoppingTask stopping = new StoppingTask(running, blocked, tp);
+
+ tp.execute(runForever);
+ tp.execute(stopping);
+ tp.execute(runForever);
+
+ assertTrue(running.await(5, TimeUnit.SECONDS));
+ blocked.countDown();
+ Thread.sleep(100); // wait until in doStop, then....
+ stopping._thread.interrupt(); // spurious interrupt
+ assertTrue(interrupted.await(5, TimeUnit.SECONDS));
+ assertTrue(stopping._completed.await(5, TimeUnit.SECONDS));
+ }
+
private int count(String s, String p)
{
int c = 0;
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java
index d4f225e1309..92ba1f8af4e 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java
@@ -18,6 +18,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
+import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileStore;
@@ -188,6 +189,7 @@ public class HugeResourceTest
context.setBaseResource(new PathResource(staticBase));
context.addServlet(PostServlet.class, "/post");
+ context.addServlet(ChunkedServlet.class, "/chunked/*");
String location = multipartTempDir.toString();
long maxFileSize = Long.MAX_VALUE;
@@ -223,7 +225,7 @@ public class HugeResourceTest
@ParameterizedTest
@MethodSource("staticFiles")
- public void testDownload(String filename, long expectedSize) throws Exception
+ public void testDownloadStatic(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
@@ -250,7 +252,33 @@ public class HugeResourceTest
@ParameterizedTest
@MethodSource("staticFiles")
- public void testHead(String filename, long expectedSize) throws Exception
+ public void testDownloadChunked(String filename, long expectedSize) throws Exception
+ {
+ URI destUri = server.getURI().resolve("/chunked/" + filename);
+ InputStreamResponseListener responseListener = new InputStreamResponseListener();
+
+ Request request = client.newRequest(destUri)
+ .method(HttpMethod.GET);
+ request.send(responseListener);
+ Response response = responseListener.get(5, TimeUnit.SECONDS);
+
+ assertThat("HTTP Response Code", response.getStatus(), is(200));
+ // dumpResponse(response);
+
+ String transferEncoding = response.getHeaders().get(HttpHeader.TRANSFER_ENCODING);
+ assertThat("Http Response Header: \"Transfer-Encoding\"", transferEncoding, is("chunked"));
+
+ try (ByteCountingOutputStream out = new ByteCountingOutputStream();
+ InputStream in = responseListener.getInputStream())
+ {
+ IO.copy(in, out);
+ assertThat("Downloaded Files Size: " + filename, out.getCount(), is(expectedSize));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("staticFiles")
+ public void testHeadStatic(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
@@ -273,6 +301,30 @@ public class HugeResourceTest
assertThat("Http Response Header: \"Content-Length: " + contentLength + "\"", contentLengthLong, is(expectedSize));
}
+ @ParameterizedTest
+ @MethodSource("staticFiles")
+ public void testHeadChunked(String filename, long expectedSize) throws Exception
+ {
+ URI destUri = server.getURI().resolve("/chunked/" + filename);
+ InputStreamResponseListener responseListener = new InputStreamResponseListener();
+
+ Request request = client.newRequest(destUri)
+ .method(HttpMethod.HEAD);
+ request.send(responseListener);
+ Response response = responseListener.get(5, TimeUnit.SECONDS);
+
+ try (InputStream in = responseListener.getInputStream())
+ {
+ assertThat(in.read(), is(-1));
+ }
+
+ assertThat("HTTP Response Code", response.getStatus(), is(200));
+ // dumpResponse(response);
+
+ String transferEncoding = response.getHeaders().get(HttpHeader.TRANSFER_ENCODING);
+ assertThat("Http Response Header: \"Transfer-Encoding\"", transferEncoding, is("chunked"));
+ }
+
@ParameterizedTest
@MethodSource("staticFiles")
public void testUpload(String filename, long expectedSize) throws Exception
@@ -359,6 +411,22 @@ public class HugeResourceTest
}
}
+ public static class ChunkedServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ URL resource = req.getServletContext().getResource(req.getPathInfo());
+ OutputStream output = resp.getOutputStream();
+ try (InputStream input = resource.openStream())
+ {
+ resp.setContentType("application/octet-stream");
+ resp.flushBuffer();
+ IO.copy(input, output);
+ }
+ }
+ }
+
public static class MultipartServlet extends HttpServlet
{
@Override
diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java
index 53f6f933d25..3b640bc05e5 100644
--- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java
+++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java
@@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.javax.common;
-import java.io.IOException;
import java.net.URI;
import java.security.Principal;
import java.time.Duration;
@@ -24,7 +23,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
@@ -35,7 +33,7 @@ import javax.websocket.RemoteEndpoint.Basic;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
-import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@@ -190,13 +188,11 @@ public class JavaxWebSocketSession implements javax.websocket.Session
{
try
{
- FutureCallback b = new FutureCallback();
- coreSession.close(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase(), b);
- b.block(getBlockingTimeout(), TimeUnit.MILLISECONDS);
+ coreSession.close(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase(), Callback.NOOP);
}
- catch (IOException e)
+ catch (Throwable t)
{
- LOG.trace("IGNORED", e);
+ LOG.trace("IGNORED", t);
}
}
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java
index 11a883b4edc..9143ca5387e 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java
@@ -13,6 +13,7 @@
package org.eclipse.jetty.websocket.javax.tests;
+import java.io.IOException;
import java.net.URI;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -72,7 +73,8 @@ public class JavaxOnCloseTest
public void onClose(CloseReason reason)
{
super.onClose(reason);
- onClose.accept(session);
+ if (onClose != null)
+ onClose.accept(session);
}
}
@@ -226,4 +228,36 @@ public class JavaxOnCloseTest
assertThat(clientEndpoint.error, instanceOf(RuntimeException.class));
assertThat(clientEndpoint.error.getMessage(), containsString("trigger onError from client onClose"));
}
+
+ @Test
+ public void testCloseFromCallback() throws Exception
+ {
+ EventSocket clientEndpoint = new EventSocket();
+ URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/");
+ client.connectToServer(clientEndpoint, uri);
+
+ OnCloseEndpoint serverEndpoint = Objects.requireNonNull(serverEndpoints.poll(5, TimeUnit.SECONDS));
+ assertTrue(serverEndpoint.openLatch.await(5, TimeUnit.SECONDS));
+
+ CountDownLatch closeSent = new CountDownLatch(1);
+ clientEndpoint.session.getAsyncRemote().sendText("GOODBYE", sendResult ->
+ {
+ try
+ {
+ clientEndpoint.session.close();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ finally
+ {
+ closeSent.countDown();
+ }
+ });
+
+ assertTrue(closeSent.await(5, TimeUnit.SECONDS));
+ assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseCodes.NORMAL_CLOSURE));
+ }
}
diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
index fc10c00f8c4..355368bb362 100644
--- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
+++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
@@ -63,6 +63,32 @@ public interface Session extends WebSocketPolicy, Closeable
*/
void close(int statusCode, String reason);
+ /**
+ * Send a websocket Close frame, with status code.
+ *
+ * This will enqueue a graceful close to the remote endpoint.
+ *
+ * @param statusCode the status code
+ * @param reason the (optional) reason. (can be null for no reason)
+ * @param callback the callback to track close frame sent (or failed)
+ * @see StatusCode
+ * @see #close()
+ * @see #close(CloseStatus)
+ * @see #disconnect()
+ */
+ default void close(int statusCode, String reason, WriteCallback callback)
+ {
+ try
+ {
+ close(statusCode, reason);
+ callback.writeSuccess();
+ }
+ catch (Throwable t)
+ {
+ callback.writeFailed(t);
+ }
+ }
+
/**
* Issue a harsh disconnect of the underlying connection.
*
diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java
index 97b191d8d22..e42cfd22c16 100644
--- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java
+++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java
@@ -20,7 +20,9 @@ package org.eclipse.jetty.websocket.api;
*/
public interface WriteCallback
{
- WriteCallback NOOP = new Adaptor();
+ WriteCallback NOOP = new WriteCallback()
+ {
+ };
/**
*
@@ -44,6 +46,7 @@ public interface WriteCallback
{
}
+ @Deprecated
class Adaptor implements WriteCallback
{
@Override
diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java
index a48d825bb87..c0c42348733 100644
--- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java
+++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java
@@ -23,7 +23,6 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.api.BatchMode;
-import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
@@ -48,37 +47,6 @@ public class JettyWebSocketRemoteEndpoint implements org.eclipse.jetty.websocket
this.batchMode = batchMode;
}
- /**
- * Initiate close of the Remote with no status code (no payload)
- *
- * @since 10.0
- */
- public void close()
- {
- close(StatusCode.NO_CODE, null);
- }
-
- /**
- * Initiate close of the Remote with specified status code and optional reason phrase
- *
- * @param statusCode the status code (must be valid and can be sent)
- * @param reason optional reason code
- * @since 10.0
- */
- public void close(int statusCode, String reason)
- {
- try
- {
- FutureCallback b = new FutureCallback();
- coreSession.close(statusCode, reason, b);
- b.block(getBlockingTimeout(), TimeUnit.MILLISECONDS);
- }
- catch (IOException e)
- {
- LOG.trace("IGNORED", e);
- }
- }
-
@Override
public void sendString(String text) throws IOException
{
diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
index 76aa6541d67..1e88f92836f 100644
--- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
+++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
@@ -18,6 +18,7 @@ import java.net.SocketAddress;
import java.time.Duration;
import java.util.Objects;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.Session;
@@ -27,6 +28,7 @@ import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketContainer;
+import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,19 +55,25 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable
@Override
public void close()
{
- remoteEndpoint.close(StatusCode.NORMAL, null);
+ coreSession.close(StatusCode.NORMAL, null, Callback.NOOP);
}
@Override
public void close(CloseStatus closeStatus)
{
- remoteEndpoint.close(closeStatus.getCode(), closeStatus.getPhrase());
+ coreSession.close(closeStatus.getCode(), closeStatus.getPhrase(), Callback.NOOP);
}
@Override
public void close(int statusCode, String reason)
{
- remoteEndpoint.close(statusCode, reason);
+ coreSession.close(statusCode, reason, Callback.NOOP);
+ }
+
+ @Override
+ public void close(int statusCode, String reason, WriteCallback callback)
+ {
+ coreSession.close(statusCode, reason, Callback.from(callback::writeSuccess, callback::writeFailed));
}
@Override
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java
index 013b471d90c..4d951d0e43d 100644
--- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java
@@ -18,6 +18,7 @@ import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -57,6 +58,7 @@ import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.exceptions.UpgradeException;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.core.server.internal.UpgradeHttpServletRequest;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
@@ -68,6 +70,7 @@ import org.junit.jupiter.api.condition.OS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -80,6 +83,7 @@ public class WebSocketOverHTTP2Test
private ServerConnector connector;
private ServerConnector tlsConnector;
private WebSocketClient wsClient;
+ private ServletContextHandler context;
private void startServer() throws Exception
{
@@ -112,7 +116,7 @@ public class WebSocketOverHTTP2Test
tlsConnector = new ServerConnector(server, 1, 1, ssl, alpn, h1s, h2s);
server.addConnector(tlsConnector);
- ServletContextHandler context = new ServletContextHandler(server, "/");
+ context = new ServletContextHandler(server, "/");
context.addServlet(new ServletHolder(servlet), "/ws/*");
JettyWebSocketServletContainerInitializer.configure(context, null);
@@ -337,6 +341,41 @@ public class WebSocketOverHTTP2Test
assertThat(cause, instanceOf(ClosedChannelException.class));
}
+ @Test
+ public void testServerTimeout() throws Exception
+ {
+ startServer();
+ JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(context.getServletContext());
+ startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
+ EchoSocket serverEndpoint = new EchoSocket();
+ container.addMapping("/specialEcho", (req, resp) -> serverEndpoint);
+
+ // Set up idle timeouts.
+ long timeout = 1000;
+ container.setIdleTimeout(Duration.ofMillis(timeout));
+ wsClient.setIdleTimeout(Duration.ZERO);
+
+ // Setup a websocket connection.
+ EventSocket clientEndpoint = new EventSocket();
+ URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/specialEcho");
+ Session session = wsClient.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS);
+ session.getRemote().sendString("hello world");
+ String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+ assertThat(received, equalTo("hello world"));
+
+ // Wait for timeout on server.
+ assertTrue(serverEndpoint.closeLatch.await(timeout * 2, TimeUnit.MILLISECONDS));
+ assertThat(serverEndpoint.closeCode, equalTo(StatusCode.SHUTDOWN));
+ assertThat(serverEndpoint.closeReason, containsStringIgnoringCase("timeout"));
+ assertNotNull(serverEndpoint.error);
+
+ // Wait for timeout on client.
+ assertTrue(clientEndpoint.closeLatch.await(timeout * 2, TimeUnit.MILLISECONDS));
+ assertThat(clientEndpoint.closeCode, equalTo(StatusCode.SHUTDOWN));
+ assertThat(clientEndpoint.closeReason, containsStringIgnoringCase("timeout"));
+ assertNull(clientEndpoint.error);
+ }
+
private static class TestJettyWebSocketServlet extends JettyWebSocketServlet
{
@Override
diff --git a/pom.xml b/pom.xml
index eba9f3a2970..e1fe911a70a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,7 +34,7 @@
4.2.0
6.3.1
1.5
- 10.3
+ 10.3.1
1.15
1.21
2.11.0
@@ -90,7 +90,7 @@
1.2
5.9
1.35
- 5.11.0
+ 5.12.1
0.10.3
0.32.13
2.2.12
@@ -104,11 +104,10 @@
2.0.2
2.17.2
1.3.0-alpha16
- 3.0.5
+ 3.0.6
10.3.6
- 0.13.1
1.8.1
- 3.8.4
+ 3.8.6
3.12.11
0.9.1
8.1.0
@@ -121,7 +120,7 @@
2.1.1.RELEASE
1.2.5
1.2.5
- 1.17.2
+ 1.17.3
3.1.9.Final
1.6.0.Final
1.19.0.Final
@@ -148,7 +147,7 @@
3.3.0
3.0.0-M2
2.10
- 3.0.0
+ 3.1.0
3.0.0
3.0.1
3.0.0-M1
@@ -1681,11 +1680,32 @@
jetty-memcached-sessions
${project.version}
+
+ org.eclipse.jetty.osgi
+ jetty-osgi-alpn
+ ${project.version}
+
org.eclipse.jetty.osgi
jetty-osgi-boot
${project.version}
+
+ org.eclipse.jetty.osgi
+ jetty-osgi-boot-jsp
+ ${project.version}
+
+
+ org.eclipse.jetty.osgi
+ jetty-osgi-boot-warurl
+ ${project.version}
+
+
+ org.eclipse.jetty.osgi
+ jetty-httpservice
+ ${project.version}
+
+
org.eclipse.jetty.quic
quic-client