Skip to content

Commit a0e0e22

Browse files
committed
Missing method to ping websockets
- fix #3813
1 parent d3eb08b commit a0e0e22

File tree

6 files changed

+231
-34
lines changed

6 files changed

+231
-34
lines changed

jooby/src/main/java/io/jooby/WebSocket.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,65 @@ default WebSocket attribute(@NonNull String key, Object value) {
206206
*/
207207
void forEach(SneakyThrows.Consumer<WebSocket> callback);
208208

209+
/**
210+
* Send a ping message to client.
211+
*
212+
* @param message Text Message.
213+
* @return This websocket.
214+
*/
215+
default WebSocket sendPing(@NonNull String message) {
216+
return sendPing(message, WriteCallback.NOOP);
217+
}
218+
219+
/**
220+
* Send a ping message to client.
221+
*
222+
* @param message Text Message.
223+
* @param callback Write callback.
224+
* @return This websocket.
225+
*/
226+
WebSocket sendPing(@NonNull String message, @NonNull WriteCallback callback);
227+
228+
/**
229+
* Send a ping message to client.
230+
*
231+
* @param message Text Message.
232+
* @return This websocket.
233+
*/
234+
default WebSocket sendPing(@NonNull byte[] message) {
235+
return sendPing(message, WriteCallback.NOOP);
236+
}
237+
238+
/**
239+
* Send a ping message to client.
240+
*
241+
* @param message Text Message.
242+
* @param callback Write callback.
243+
* @return This websocket.
244+
*/
245+
default WebSocket sendPing(byte[] message, @NonNull WriteCallback callback) {
246+
return sendPing(ByteBuffer.wrap(message), callback);
247+
}
248+
249+
/**
250+
* Send a ping message to client.
251+
*
252+
* @param message Text message.
253+
* @return This instance.
254+
*/
255+
default WebSocket sendPing(@NonNull ByteBuffer message) {
256+
return sendPing(message, WriteCallback.NOOP);
257+
}
258+
259+
/**
260+
* Send a ping message to client.
261+
*
262+
* @param message Text message.
263+
* @param callback Write callback.
264+
* @return This instance.
265+
*/
266+
WebSocket sendPing(@NonNull ByteBuffer message, @NonNull WriteCallback callback);
267+
209268
/**
210269
* Send a text message to client.
211270
*

modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyWebSocket.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,21 @@ public void forEach(SneakyThrows.Consumer<WebSocket> callback) {
237237
}
238238
}
239239

240+
@NonNull @Override
241+
public WebSocket sendPing(@NonNull String message, @NonNull WriteCallback callback) {
242+
return sendMessage(
243+
(remote, writeCallback) ->
244+
remote.sendPing(ByteBuffer.wrap(message.getBytes(UTF_8)), writeCallback),
245+
new WriteCallbackAdaptor(this, callback));
246+
}
247+
248+
@NonNull @Override
249+
public WebSocket sendPing(@NonNull ByteBuffer message, @NonNull WriteCallback callback) {
250+
return sendMessage(
251+
(remote, writeCallback) -> remote.sendPing(message, writeCallback),
252+
new WriteCallbackAdaptor(this, callback));
253+
}
254+
240255
@NonNull @Override
241256
public WebSocket sendBinary(@NonNull String message, @NonNull WriteCallback callback) {
242257
return sendMessage(

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWebSocket.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,12 @@ private WebSocket renderMessage(Object value, boolean binary, WriteCallback call
169169
}
170170

171171
private WebSocket sendMessage(ByteBuf buffer, boolean binary, WriteCallback callback) {
172+
return sendMessage(
173+
binary ? new BinaryWebSocketFrame(buffer) : new TextWebSocketFrame(buffer), callback);
174+
}
175+
176+
private WebSocket sendMessage(WebSocketFrame frame, WriteCallback callback) {
172177
if (isOpen()) {
173-
WebSocketFrame frame =
174-
binary ? new BinaryWebSocketFrame(buffer) : new TextWebSocketFrame(buffer);
175178
netty
176179
.ctx
177180
.channel()
@@ -217,6 +220,18 @@ public void forEach(SneakyThrows.Consumer<WebSocket> callback) {
217220
}
218221
}
219222

223+
@NonNull @Override
224+
public WebSocket sendPing(@NonNull String message, @NonNull WriteCallback callback) {
225+
return sendMessage(
226+
new PingWebSocketFrame(Unpooled.wrappedBuffer(message.getBytes(StandardCharsets.UTF_8))),
227+
callback);
228+
}
229+
230+
@Override
231+
public WebSocket sendPing(@NonNull ByteBuffer message, @NonNull WriteCallback callback) {
232+
return sendMessage(new PingWebSocketFrame(Unpooled.wrappedBuffer(message)), callback);
233+
}
234+
220235
@Override
221236
public WebSocketConfigurer onConnect(WebSocket.OnConnect callback) {
222237
connectCallback = callback;

modules/jooby-test/src/main/java/io/jooby/test/MockWebSocket.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,17 @@ public void forEach(SneakyThrows.Consumer<WebSocket> callback) {
7676
callback.accept(this);
7777
}
7878

79-
@NonNull @Override
79+
@Override
80+
public WebSocket sendPing(@NonNull String message, @NonNull WriteCallback callback) {
81+
return sendObject(message, callback);
82+
}
83+
84+
@Override
85+
public WebSocket sendPing(@NonNull ByteBuffer message, @NonNull WriteCallback callback) {
86+
return sendObject(message, callback);
87+
}
88+
89+
@Override
8090
public WebSocket send(@NonNull String message, @NonNull WriteCallback callback) {
8191
return sendObject(message, callback);
8292
}

modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowWebSocket.java

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,18 @@
3434
import io.jooby.WebSocketConfigurer;
3535
import io.jooby.WebSocketMessage;
3636
import io.jooby.output.Output;
37-
import io.undertow.websockets.core.AbstractReceiveListener;
38-
import io.undertow.websockets.core.BufferedBinaryMessage;
39-
import io.undertow.websockets.core.BufferedTextMessage;
40-
import io.undertow.websockets.core.CloseMessage;
41-
import io.undertow.websockets.core.WebSocketCallback;
42-
import io.undertow.websockets.core.WebSocketChannel;
43-
import io.undertow.websockets.core.WebSockets;
37+
import io.undertow.websockets.core.*;
4438

4539
public class UndertowWebSocket extends AbstractReceiveListener
4640
implements WebSocketConfigurer, WebSocket {
4741

42+
enum FrameType {
43+
TEXT,
44+
BINARY,
45+
PING,
46+
PONG;
47+
}
48+
4849
private static class WriteCallbackAdaptor implements WebSocketCallback<Void> {
4950

5051
protected final UndertowWebSocket ws;
@@ -89,7 +90,7 @@ public void onError(WebSocketChannel channel, Void context, Throwable cause) {
8990

9091
private static class WebSocketOutputCallback implements WebSocketCallback<Void> {
9192
private final Iterator<ByteBuffer> it;
92-
private final boolean binary;
93+
private final FrameType type;
9394
private final WebSocketChannel channel;
9495
private final UndertowWebSocket ws;
9596
private final WriteCallback cb;
@@ -98,11 +99,11 @@ public WebSocketOutputCallback(
9899
UndertowWebSocket ws,
99100
WebSocketChannel channel,
100101
WriteCallback callback,
101-
boolean binary,
102+
FrameType type,
102103
Output buffer) {
103104
this.ws = ws;
104105
this.channel = channel;
105-
this.binary = binary;
106+
this.type = type;
106107
this.cb = callback;
107108
this.it = buffer.iterator();
108109
}
@@ -112,11 +113,7 @@ public void send() {
112113
try {
113114
var buffer = it.next();
114115
WebSocketCallback<Void> callback = it.hasNext() ? this : new WriteCallbackAdaptor(ws, cb);
115-
if (binary) {
116-
WebSockets.sendBinary(buffer, channel, callback);
117-
} else {
118-
WebSockets.sendText(buffer, channel, callback);
119-
}
116+
UndertowWebSocket.sendMessage(ws, buffer, type, callback);
120117
} catch (Throwable x) {
121118
ws.onError(channel, x);
122119
}
@@ -207,40 +204,53 @@ public void forEach(SneakyThrows.Consumer<WebSocket> callback) {
207204
}
208205
}
209206

207+
@NonNull @Override
208+
public WebSocket sendPing(@NonNull String message, @NonNull WriteCallback callback) {
209+
return sendMessage(
210+
ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), FrameType.PING, callback);
211+
}
212+
213+
@NonNull @Override
214+
public WebSocket sendPing(@NonNull ByteBuffer message, @NonNull WriteCallback callback) {
215+
return sendMessage(message, FrameType.PING, callback);
216+
}
217+
210218
@NonNull @Override
211219
public WebSocket send(@NonNull String message, @NonNull WriteCallback callback) {
212-
return sendMessage(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), false, callback);
220+
return sendMessage(
221+
ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), FrameType.TEXT, callback);
213222
}
214223

215224
@NonNull @Override
216225
public WebSocket send(@NonNull ByteBuffer message, @NonNull WriteCallback callback) {
217-
return sendMessage(message, false, callback);
226+
return sendMessage(message, FrameType.TEXT, callback);
218227
}
219228

220229
@NonNull @Override
221230
public WebSocket sendBinary(@NonNull String message, @NonNull WriteCallback callback) {
222-
return sendMessage(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), true, callback);
231+
return sendMessage(
232+
ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), FrameType.BINARY, callback);
223233
}
224234

225235
@NonNull @Override
226236
public WebSocket sendBinary(@NonNull Output message, @NonNull WriteCallback callback) {
227-
return sendMessage(message, true, callback);
237+
return sendMessage(message, FrameType.BINARY, callback);
228238
}
229239

230240
@NonNull @Override
231241
public WebSocket send(@NonNull Output message, @NonNull WriteCallback callback) {
232-
return sendMessage(message, false, callback);
242+
return sendMessage(message, FrameType.TEXT, callback);
233243
}
234244

235245
@NonNull @Override
236246
public WebSocket sendBinary(@NonNull ByteBuffer message, @NonNull WriteCallback callback) {
237-
return sendMessage(message, true, callback);
247+
return sendMessage(message, FrameType.BINARY, callback);
238248
}
239249

240-
private WebSocket sendMessage(Output buffer, boolean binary, WriteCallback callback) {
250+
private WebSocket sendMessage(Output buffer, FrameType type, WriteCallback callback) {
241251
if (isOpen()) {
242252
try {
243-
new WebSocketOutputCallback(this, channel, callback, binary, buffer).send();
253+
new WebSocketOutputCallback(this, channel, callback, type, buffer).send();
244254
} catch (Throwable x) {
245255
onError(channel, x);
246256
}
@@ -250,21 +260,36 @@ private WebSocket sendMessage(Output buffer, boolean binary, WriteCallback callb
250260
return this;
251261
}
252262

253-
private WebSocket sendMessage(ByteBuffer buffer, boolean binary, WriteCallback callback) {
254-
if (isOpen()) {
263+
private WebSocket sendMessage(ByteBuffer buffer, FrameType type, WriteCallback callback) {
264+
265+
return UndertowWebSocket.sendMessage(
266+
this, buffer, type, new WriteCallbackAdaptor(this, callback));
267+
}
268+
269+
static WebSocket sendMessage(
270+
UndertowWebSocket ws, ByteBuffer buffer, FrameType type, WebSocketCallback<Void> callback) {
271+
if (ws.isOpen()) {
255272
try {
256-
if (binary) {
257-
WebSockets.sendBinary(buffer, channel, new WriteCallbackAdaptor(this, callback));
273+
if (type == FrameType.BINARY) {
274+
WebSockets.sendBinary(buffer, ws.channel, callback);
275+
} else if (type == FrameType.TEXT) {
276+
WebSockets.sendText(buffer, ws.channel, callback);
277+
} else if (type == FrameType.PING) {
278+
WebSockets.sendPing(buffer, ws.channel, callback);
279+
} else if (type == FrameType.PONG) {
280+
WebSockets.sendPong(buffer, ws.channel, callback);
258281
} else {
259-
WebSockets.sendText(buffer, channel, new WriteCallbackAdaptor(this, callback));
282+
throw new IllegalStateException("Unknown frame type: " + type);
260283
}
284+
261285
} catch (Throwable x) {
262-
onError(channel, x);
286+
ws.onError(ws.channel, x);
263287
}
264288
} else {
265-
onError(channel, new IllegalStateException("Attempt to send a message on closed web socket"));
289+
ws.onError(
290+
ws.channel, new IllegalStateException("Attempt to send a message on closed web socket"));
266291
}
267-
return this;
292+
return ws;
268293
}
269294

270295
@NonNull @Override
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.i3813;
7+
8+
import edu.umd.cs.findbugs.annotations.NonNull;
9+
import edu.umd.cs.findbugs.annotations.Nullable;
10+
import io.jooby.junit.ServerTest;
11+
import io.jooby.junit.ServerTestRunner;
12+
import okhttp3.Response;
13+
import okhttp3.WebSocket;
14+
import okhttp3.WebSocketListener;
15+
import okio.ByteString;
16+
17+
public class Issue3813 {
18+
19+
@ServerTest
20+
public void shouldSendPingMessage(ServerTestRunner runner) {
21+
runner
22+
.define(
23+
app -> {
24+
app.ws(
25+
"/3813",
26+
(ctx, initializer) -> {
27+
initializer.onConnect(
28+
ws -> {
29+
ws.sendPing("Hello, World!");
30+
});
31+
});
32+
})
33+
.ready(
34+
client -> {
35+
client.webSocket(
36+
"/3813",
37+
new WebSocketListener() {
38+
@Override
39+
public void onOpen(@NonNull WebSocket ws, @NonNull Response response) {}
40+
41+
@Override
42+
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
43+
super.onMessage(webSocket, text);
44+
}
45+
46+
@Override
47+
public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
48+
super.onMessage(webSocket, bytes);
49+
}
50+
51+
@Override
52+
public void onClosing(
53+
@NonNull WebSocket webSocket, int code, @NonNull String reason) {
54+
super.onClosing(webSocket, code, reason);
55+
}
56+
57+
@Override
58+
public void onClosed(
59+
@NonNull WebSocket webSocket, int code, @NonNull String reason) {
60+
super.onClosed(webSocket, code, reason);
61+
}
62+
63+
@Override
64+
public void onFailure(
65+
@NonNull WebSocket webSocket,
66+
@NonNull Throwable t,
67+
@Nullable Response response) {
68+
super.onFailure(webSocket, t, response);
69+
}
70+
});
71+
});
72+
}
73+
}

0 commit comments

Comments
 (0)