Skip to content

Commit 6d446ff

Browse files
baranowbfl4via
authored andcommitted
[UNDERTOW-2333] Add websocket timeout testcase
1 parent 566df6d commit 6d446ff

File tree

3 files changed

+210
-8
lines changed

3 files changed

+210
-8
lines changed

core/src/test/java/io/undertow/testutils/DefaultServer.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ public static boolean startServer() {
443443
} else {
444444
server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), 7777 + PROXY_OFFSET), acceptListener, serverOptions);
445445

446-
proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true));
446+
proxyOpenListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).getMap());
447447
proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener));
448448
proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions);
449449
loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER)
@@ -466,7 +466,7 @@ public static boolean startServer() {
466466
server = ssl.createSslConnectionServer(worker, new InetSocketAddress(getHostAddress("default"), 7777 + PROXY_OFFSET), acceptListener, serverOptions);
467467
server.resumeAccepts();
468468

469-
proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true));
469+
proxyOpenListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).getMap());
470470
proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener));
471471
proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions);
472472
loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER)
@@ -488,13 +488,13 @@ public static boolean startServer() {
488488
proxyOpenListener.setRootHandler(proxyHandler);
489489
proxyServer.resumeAccepts();
490490
} else if (h2c || h2cUpgrade) {
491-
openListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true, UndertowOptions.HTTP2_PADDING_SIZE, 10));
491+
openListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.ENABLE_HTTP2, true).set(UndertowOptions.HTTP2_PADDING_SIZE, 10).getMap());
492492
acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener));
493493

494494
InetSocketAddress targetAddress = new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT) + PROXY_OFFSET);
495495
server = worker.createStreamConnectionServer(targetAddress, acceptListener, serverOptions);
496496

497-
proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true));
497+
proxyOpenListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).getMap());
498498
proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener));
499499
proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions);
500500
loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER)
@@ -519,13 +519,13 @@ public static boolean startServer() {
519519

520520
} else if (https) {
521521
XnioSsl clientSsl = new UndertowXnioSsl(xnio, OptionMap.EMPTY, SSL_BUFFER_POOL, createClientSslContext());
522-
openListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true));
522+
openListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).getMap());
523523
acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener));
524524
server = ssl.createSslConnectionServer(worker, new InetSocketAddress(getHostAddress("default"), 7777 + PROXY_OFFSET), acceptListener, serverOptions);
525525
server.getAcceptSetter().set(acceptListener);
526526
server.resumeAccepts();
527527

528-
proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true));
528+
proxyOpenListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).getMap());
529529
proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener));
530530
proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions);
531531
loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER)
@@ -545,15 +545,15 @@ public static boolean startServer() {
545545
if (h2) {
546546
UndertowLogger.ROOT_LOGGER.error("HTTP2 selected but Netty ALPN was not on the boot class path");
547547
}
548-
openListener = new HttpOpenListener(pool, OptionMap.builder().set(UndertowOptions.BUFFER_PIPELINED_DATA, true).set(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, true).set(UndertowOptions.REQUIRE_HOST_HTTP11, true).getMap());
548+
openListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).set(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, true).set(UndertowOptions.REQUIRE_HOST_HTTP11, true).getMap());
549549
acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener));
550550
if (!proxy) {
551551
server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), acceptListener, serverOptions);
552552
} else {
553553
InetSocketAddress targetAddress = new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT) + PROXY_OFFSET);
554554
server = worker.createStreamConnectionServer(targetAddress, acceptListener, serverOptions);
555555

556-
proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true));
556+
proxyOpenListener = new HttpOpenListener(pool, OptionMap.builder().addAll(serverOptions).set(UndertowOptions.BUFFER_PIPELINED_DATA, true).getMap());
557557
proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener));
558558
proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions);
559559
loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2023 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package io.undertow.websockets.core.protocol;
19+
20+
import java.io.IOException;
21+
import java.net.URI;
22+
import java.util.concurrent.Executors;
23+
import java.util.concurrent.ScheduledExecutorService;
24+
import java.util.concurrent.ScheduledFuture;
25+
import java.util.concurrent.TimeUnit;
26+
import java.util.concurrent.atomic.AtomicBoolean;
27+
28+
import org.junit.After;
29+
import org.junit.Assert;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.xnio.FutureResult;
33+
import org.xnio.OptionMap;
34+
import org.xnio.Options;
35+
36+
import io.netty.buffer.Unpooled;
37+
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
38+
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
39+
import io.netty.util.CharsetUtil;
40+
import io.undertow.testutils.DefaultServer;
41+
import io.undertow.testutils.HttpOneOnly;
42+
import io.undertow.util.NetworkUtils;
43+
import io.undertow.websockets.WebSocketConnectionCallback;
44+
import io.undertow.websockets.WebSocketProtocolHandshakeHandler;
45+
import io.undertow.websockets.core.AbstractReceiveListener;
46+
import io.undertow.websockets.core.BufferedTextMessage;
47+
import io.undertow.websockets.core.WebSocketChannel;
48+
import io.undertow.websockets.core.WebSockets;
49+
import io.undertow.websockets.spi.WebSocketHttpExchange;
50+
import io.undertow.websockets.utils.FrameChecker;
51+
import io.undertow.websockets.utils.WebSocketTestClient;
52+
53+
@RunWith(DefaultServer.class)
54+
@HttpOneOnly
55+
public class WebSocketTimeoutTestCase {
56+
57+
protected void beforeTest(int regularTimeouts, int wsReadTimeout, int wsWriteTimeout) {
58+
DefaultServer.stopServer();
59+
DefaultServer.setServerOptions(OptionMap.builder()
60+
.set(Options.READ_TIMEOUT, regularTimeouts)
61+
.set(Options.WRITE_TIMEOUT, regularTimeouts).getMap());
62+
63+
DefaultServer.setUndertowOptions(OptionMap.builder()
64+
.set(Options.READ_TIMEOUT, regularTimeouts)
65+
.set(Options.WRITE_TIMEOUT, regularTimeouts).getMap());
66+
DefaultServer.startServer();
67+
SCHEDULER = Executors.newScheduledThreadPool(2);
68+
System.setProperty(WebSocketChannel.WEB_SOCKETS_READ_TIMEOUT, ""+wsReadTimeout);
69+
System.setProperty(WebSocketChannel.WEB_SOCKETS_WRITE_TIMEOUT, ""+wsWriteTimeout);
70+
}
71+
72+
@After
73+
public void afterTest() {
74+
DefaultServer.stopServer();
75+
DefaultServer.setServerOptions(OptionMap.EMPTY);
76+
DefaultServer.setUndertowOptions(OptionMap.EMPTY);
77+
SCHEDULER.shutdown();
78+
System.clearProperty(WebSocketChannel.WEB_SOCKETS_READ_TIMEOUT);
79+
System.clearProperty(WebSocketChannel.WEB_SOCKETS_WRITE_TIMEOUT);
80+
}
81+
82+
protected static final int TESTABLE_TIMEOUT_VALUE = 2000;
83+
protected static final int NON_TESTABLE_TIMEOUT_VALUE = 30180;
84+
protected static final int DEFAULTS_IO_TIMEOTU_VALUE = 500;
85+
private ScheduledExecutorService SCHEDULER;
86+
87+
protected WebSocketVersion getVersion() {
88+
return WebSocketVersion.V13;
89+
}
90+
91+
92+
@Test
93+
public void testServerReadTimeout() throws Exception {
94+
beforeTest(DEFAULTS_IO_TIMEOTU_VALUE, TESTABLE_TIMEOUT_VALUE, NON_TESTABLE_TIMEOUT_VALUE);
95+
final AtomicBoolean connected = new AtomicBoolean(false);
96+
DefaultServer.setRootHandler(new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() {
97+
@Override
98+
public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) {
99+
connected.set(true);
100+
channel.getReceiveSetter().set(new AbstractReceiveListener() {
101+
@Override
102+
protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException {
103+
String string = message.getData();
104+
105+
if (string.equals("hello")) {
106+
WebSockets.sendText("world", channel, null);
107+
} else {
108+
WebSockets.sendText(string, channel, null);
109+
}
110+
}
111+
});
112+
channel.resumeReceives();
113+
}
114+
}));
115+
116+
final FutureResult<?> latch = new FutureResult();
117+
WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ":" + DefaultServer.getHostPort("default") + "/"));
118+
client.connect();
119+
client.send(new TextWebSocketFrame(Unpooled.copiedBuffer("hello", CharsetUtil.US_ASCII)), new FrameChecker(TextWebSocketFrame.class, "world".getBytes(CharsetUtil.US_ASCII), latch));
120+
latch.getIoFuture().get();
121+
122+
final long watchStart = System.currentTimeMillis();
123+
final long watchTimeout = System.currentTimeMillis()+TESTABLE_TIMEOUT_VALUE+500;
124+
final FutureResult<Long> timeoutLatch = new FutureResult<Long>();
125+
ReadTimeoutChannelGuard readTimeoutChannelGuard = new ReadTimeoutChannelGuard(client, timeoutLatch, watchTimeout);
126+
127+
final ScheduledFuture sf = SCHEDULER.scheduleAtFixedRate(readTimeoutChannelGuard, 0, 50, TimeUnit.MILLISECONDS);
128+
readTimeoutChannelGuard.setTaskScheduledFuture(sf);
129+
130+
final Long watchTimeEnd = timeoutLatch.getIoFuture().get();
131+
if(watchTimeEnd == -1) {
132+
Assert.fail("Timeout did not happen... in time. Were waiting '"+watchTimeout+"' ms, timeout should happen in '"+TESTABLE_TIMEOUT_VALUE+"' ms.");
133+
} else {
134+
long timeSpent = watchTimeEnd - watchStart;
135+
//lets be generous and give 150ms diff( there is "fuzz" coded for 50ms in undertow as well
136+
if(!(timeSpent<=TESTABLE_TIMEOUT_VALUE+150)) {
137+
Assert.fail("Timeout did not happen... in time. Socket timeout out in '"+timeSpent+"' ms, supposed to happen in '"+TESTABLE_TIMEOUT_VALUE+"' ms.");
138+
}
139+
}
140+
}
141+
142+
private static class ReadTimeoutChannelGuard implements Runnable{
143+
private final WebSocketTestClient channel;
144+
private final FutureResult<Long> resultHandler;
145+
private final long watchEnd;
146+
private ScheduledFuture<?> sf;
147+
148+
ReadTimeoutChannelGuard(final WebSocketTestClient channel, final FutureResult<Long> resultHandler, final long watchEnd) {
149+
super();
150+
this.channel = channel;
151+
this.resultHandler = resultHandler;
152+
this.watchEnd = watchEnd;
153+
}
154+
155+
public void setTaskScheduledFuture(ScheduledFuture sf2) {
156+
this.sf = sf2;
157+
}
158+
159+
@Override
160+
public void run() {
161+
if(System.currentTimeMillis() > watchEnd) {
162+
sf.cancel(false);
163+
if(channelActive()) {
164+
resultHandler.setResult(new Long(-1));
165+
} else {
166+
resultHandler.setResult(System.currentTimeMillis());
167+
}
168+
} else {
169+
if(!channelActive()) {
170+
sf.cancel(false);
171+
resultHandler.setResult(System.currentTimeMillis());
172+
}
173+
}
174+
}
175+
176+
private boolean channelActive() {
177+
return channel.isOpen();
178+
}
179+
180+
}
181+
}

core/src/test/java/io/undertow/websockets/utils/WebSocketTestClient.java

+21
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,27 @@ public void onError(Throwable t) {
173173
}
174174
}
175175

176+
public boolean isActive() {
177+
if(this.ch != null) {
178+
return this.ch.isActive();
179+
}
180+
return false;
181+
}
182+
183+
public boolean isOpen() {
184+
if(this.ch != null) {
185+
return this.ch.isOpen();
186+
}
187+
return false;
188+
}
189+
190+
public boolean isWritable() {
191+
if(this.ch != null) {
192+
return this.ch.isWritable();
193+
}
194+
return false;
195+
}
196+
176197
public interface FrameListener {
177198
/**
178199
* Is called if an WebSocketFrame was received

0 commit comments

Comments
 (0)