Skip to content

Commit f5ed6c3

Browse files
committed
WebSockets Next: introduce connection-idle-timeout config property
1 parent 5370844 commit f5ed6c3

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package io.quarkus.websockets.next.test.connection;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.net.URI;
7+
import java.util.concurrent.CountDownLatch;
8+
import java.util.concurrent.ExecutorService;
9+
import java.util.concurrent.Executors;
10+
import java.util.concurrent.TimeUnit;
11+
import java.util.concurrent.atomic.AtomicBoolean;
12+
13+
import jakarta.inject.Inject;
14+
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.RegisterExtension;
17+
18+
import io.quarkus.test.QuarkusUnitTest;
19+
import io.quarkus.test.common.http.TestHTTPResource;
20+
import io.quarkus.websockets.next.OnClose;
21+
import io.quarkus.websockets.next.OnOpen;
22+
import io.quarkus.websockets.next.OnTextMessage;
23+
import io.quarkus.websockets.next.WebSocket;
24+
import io.quarkus.websockets.next.WebSocketClient;
25+
import io.quarkus.websockets.next.WebSocketClientConnection;
26+
import io.quarkus.websockets.next.WebSocketConnector;
27+
import io.quarkus.websockets.next.test.utils.WSClient;
28+
29+
public class ConnectionIdleTimeoutTest {
30+
31+
@RegisterExtension
32+
public static final QuarkusUnitTest test = new QuarkusUnitTest()
33+
.withApplicationRoot(root -> {
34+
root.addClasses(ServerEndpoint.class, ClientEndpoint.class, WSClient.class);
35+
}).overrideConfigKey("quarkus.websockets-next.client.connection-idle-timeout", "500ms");;
36+
37+
@TestHTTPResource("/")
38+
URI uri;
39+
40+
@Inject
41+
WebSocketConnector<ClientEndpoint> connector;
42+
43+
@Test
44+
public void testTimeout() throws InterruptedException {
45+
WebSocketClientConnection conn = connector.baseUri(uri.toString()).connectAndAwait();
46+
ExecutorService executor = Executors.newSingleThreadExecutor();
47+
try {
48+
TimeUnit.MILLISECONDS.sleep(500);
49+
executor.execute(() -> {
50+
try {
51+
conn.sendTextAndAwait("ok");
52+
} catch (Throwable ignored) {
53+
}
54+
});
55+
} finally {
56+
executor.shutdownNow();
57+
}
58+
assertTrue(ServerEndpoint.CLOSED.await(5, TimeUnit.SECONDS));
59+
assertTrue(ClientEndpoint.CLOSED.await(5, TimeUnit.SECONDS));
60+
assertFalse(ServerEndpoint.MESSAGE.get());
61+
}
62+
63+
@WebSocket(path = "/end")
64+
public static class ServerEndpoint {
65+
66+
static final CountDownLatch CLOSED = new CountDownLatch(1);
67+
static final AtomicBoolean MESSAGE = new AtomicBoolean();
68+
69+
@OnTextMessage
70+
void onText(String message) {
71+
MESSAGE.set(true);
72+
}
73+
74+
@OnClose
75+
void close() {
76+
CLOSED.countDown();
77+
}
78+
79+
}
80+
81+
@WebSocketClient(path = "/end")
82+
public static class ClientEndpoint {
83+
84+
static final CountDownLatch CLOSED = new CountDownLatch(1);
85+
86+
@OnOpen
87+
void open() {
88+
}
89+
90+
@OnClose
91+
void close(WebSocketClientConnection conn) {
92+
CLOSED.countDown();
93+
}
94+
95+
}
96+
}

extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorBase.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.net.URI;
44
import java.net.URLEncoder;
55
import java.nio.charset.StandardCharsets;
6+
import java.time.Duration;
67
import java.util.ArrayList;
78
import java.util.HashMap;
89
import java.util.HashSet;
@@ -11,6 +12,7 @@
1112
import java.util.Objects;
1213
import java.util.Optional;
1314
import java.util.Set;
15+
import java.util.concurrent.TimeUnit;
1416
import java.util.function.Consumer;
1517
import java.util.regex.Matcher;
1618
import java.util.regex.Pattern;
@@ -181,7 +183,16 @@ protected WebSocketClientOptions populateClientOptions() {
181183
if (config.maxFrameSize().isPresent()) {
182184
clientOptions.setMaxFrameSize(config.maxFrameSize().getAsInt());
183185
}
184-
186+
if (config.connectionIdleTimeout().isPresent()) {
187+
Duration timeout = config.connectionIdleTimeout().get();
188+
if (timeout.toMillis() > Integer.MAX_VALUE) {
189+
clientOptions.setIdleTimeoutUnit(TimeUnit.SECONDS);
190+
clientOptions.setIdleTimeout((int) timeout.toSeconds());
191+
} else {
192+
clientOptions.setIdleTimeoutUnit(TimeUnit.MILLISECONDS);
193+
clientOptions.setIdleTimeout((int) timeout.toMillis());
194+
}
195+
}
185196
Optional<TlsConfiguration> maybeTlsConfiguration = TlsConfiguration.from(tlsConfigurationRegistry,
186197
Optional.ofNullable(tlsConfigurationName));
187198
if (maybeTlsConfiguration.isEmpty()) {

extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/config/WebSocketsClientRuntimeConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public interface WebSocketsClientRuntimeConfig {
4747
*/
4848
Optional<Duration> autoPingInterval();
4949

50+
/**
51+
* If set then a connection will be closed if no data is received nor sent within the given timeout.
52+
*/
53+
Optional<Duration> connectionIdleTimeout();
54+
5055
/**
5156
* The strategy used when an error occurs but no error handler can handle the failure.
5257
* <p>

0 commit comments

Comments
 (0)