Skip to content

Commit 1f89732

Browse files
committed
Add support for Ping and Pong WebSocket messages
Issue: SPR-10876
1 parent cbdb99c commit 1f89732

File tree

10 files changed

+165
-11
lines changed

10 files changed

+165
-11
lines changed

spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ public final class BinaryMessage extends WebSocketMessage<ByteBuffer> {
3131

3232
/**
3333
* Create a new {@link BinaryMessage} instance.
34-
* @param payload a non-null payload
34+
* @param payload the non-null payload
3535
*/
3636
public BinaryMessage(ByteBuffer payload) {
3737
this(payload, true);
3838
}
3939

4040
/**
4141
* Create a new {@link BinaryMessage} instance.
42-
* @param payload a non-null payload
42+
* @param payload the non-null payload
4343
* @param isLast if the message is the last of a series of partial messages
4444
*/
4545
public BinaryMessage(ByteBuffer payload, boolean isLast) {
@@ -49,15 +49,15 @@ public BinaryMessage(ByteBuffer payload, boolean isLast) {
4949

5050
/**
5151
* Create a new {@link BinaryMessage} instance.
52-
* @param payload a non-null payload
52+
* @param payload the non-null payload
5353
*/
5454
public BinaryMessage(byte[] payload) {
5555
this(payload, 0, (payload == null ? 0 : payload.length), true);
5656
}
5757

5858
/**
5959
* Create a new {@link BinaryMessage} instance.
60-
* @param payload a non-null payload
60+
* @param payload the non-null payload
6161
* @param isLast if the message is the last of a series of partial messages
6262
*/
6363
public BinaryMessage(byte[] payload, boolean isLast) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.socket;
18+
19+
import java.nio.ByteBuffer;
20+
21+
/**
22+
* A WebSocket ping message.
23+
*
24+
* @author Rossen Stoyanchev
25+
* @since 4.0
26+
*/
27+
public final class PingMessage extends WebSocketMessage<ByteBuffer> {
28+
29+
30+
public PingMessage(ByteBuffer payload) {
31+
super(payload);
32+
}
33+
34+
@Override
35+
protected int getPayloadSize() {
36+
return (getPayload() != null) ? getPayload().remaining() : 0;
37+
}
38+
39+
@Override
40+
protected String toStringPayload() {
41+
return (getPayload() != null) ? getPayload().toString() : null;
42+
}
43+
44+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.socket;
18+
19+
import java.nio.ByteBuffer;
20+
21+
/**
22+
* A WebSocket pong message.
23+
*
24+
* @author Rossen Stoyanchev
25+
* @since 4.0
26+
*/
27+
public final class PongMessage extends WebSocketMessage<ByteBuffer> {
28+
29+
30+
public PongMessage(ByteBuffer payload) {
31+
super(payload);
32+
}
33+
34+
@Override
35+
protected int getPayloadSize() {
36+
return (getPayload() != null) ? getPayload().remaining() : 0;
37+
}
38+
39+
@Override
40+
protected String toStringPayload() {
41+
return (getPayload() != null) ? getPayload().toString() : null;
42+
}
43+
44+
}

spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ public final class TextMessage extends WebSocketMessage<String> {
2626

2727
/**
2828
* Create a new {@link TextMessage} instance.
29-
* @param payload the payload
29+
* @param payload the non-null payload
3030
*/
3131
public TextMessage(CharSequence payload) {
3232
super(payload.toString(), true);
3333
}
3434

3535
/**
3636
* Create a new {@link TextMessage} instance.
37-
* @param payload the payload
37+
* @param payload the non-null payload
3838
* @param isLast whether this the last part of a message received or transmitted in parts
3939
*/
4040
public TextMessage(CharSequence payload, boolean isLast) {

spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
*
2525
* @author Rossen Stoyanchev
2626
* @since 4.0
27-
* @see BinaryMessage
28-
* @see TextMessage
2927
*/
3028
public abstract class WebSocketMessage<T> {
3129

@@ -34,12 +32,19 @@ public abstract class WebSocketMessage<T> {
3432
private final boolean last;
3533

3634

35+
/**
36+
* Create a new {@link WebSocketMessage} instance with the given payload.
37+
*/
38+
WebSocketMessage(T payload) {
39+
this(payload, true);
40+
}
41+
3742
/**
3843
* Create a new {@link WebSocketMessage} instance with the given payload.
3944
* @param payload a non-null payload
4045
*/
4146
WebSocketMessage(T payload, boolean isLast) {
42-
Assert.notNull(payload, "Payload must not be null");
47+
Assert.notNull(payload, "payload is required");
4348
this.payload = payload;
4449
this.last = isLast;
4550
}

spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssion.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.springframework.util.Assert;
2424
import org.springframework.web.socket.BinaryMessage;
2525
import org.springframework.web.socket.CloseStatus;
26+
import org.springframework.web.socket.PingMessage;
27+
import org.springframework.web.socket.PongMessage;
2628
import org.springframework.web.socket.TextMessage;
2729
import org.springframework.web.socket.WebSocketMessage;
2830
import org.springframework.web.socket.WebSocketSession;
@@ -101,15 +103,24 @@ public final void sendMessage(WebSocketMessage message) throws IOException {
101103
else if (message instanceof BinaryMessage) {
102104
sendBinaryMessage((BinaryMessage) message);
103105
}
106+
else if (message instanceof PingMessage) {
107+
sendPingMessage((PingMessage) message);
108+
}
109+
else if (message instanceof PongMessage) {
110+
sendPongMessage((PongMessage) message);
111+
}
104112
else {
105113
throw new IllegalStateException("Unexpected WebSocketMessage type: " + message);
106114
}
107115
}
108116

109-
protected abstract void sendTextMessage(TextMessage message) throws IOException ;
117+
protected abstract void sendTextMessage(TextMessage message) throws IOException;
118+
119+
protected abstract void sendBinaryMessage(BinaryMessage message) throws IOException;
110120

111-
protected abstract void sendBinaryMessage(BinaryMessage message) throws IOException ;
121+
protected abstract void sendPingMessage(PingMessage message) throws IOException;
112122

123+
protected abstract void sendPongMessage(PongMessage message) throws IOException;
113124

114125
@Override
115126
public final void close() throws IOException {

spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSession.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.springframework.util.ObjectUtils;
2727
import org.springframework.web.socket.BinaryMessage;
2828
import org.springframework.web.socket.CloseStatus;
29+
import org.springframework.web.socket.PingMessage;
30+
import org.springframework.web.socket.PongMessage;
2931
import org.springframework.web.socket.TextMessage;
3032
import org.springframework.web.socket.WebSocketSession;
3133

@@ -117,6 +119,16 @@ protected void sendBinaryMessage(BinaryMessage message) throws IOException {
117119
getNativeSession().getRemote().sendBytes(message.getPayload());
118120
}
119121

122+
@Override
123+
protected void sendPingMessage(PingMessage message) throws IOException {
124+
getNativeSession().getRemote().sendPing(message.getPayload());
125+
}
126+
127+
@Override
128+
protected void sendPongMessage(PongMessage message) throws IOException {
129+
getNativeSession().getRemote().sendPong(message.getPayload());
130+
}
131+
120132
@Override
121133
protected void closeInternal(CloseStatus status) throws IOException {
122134
getNativeSession().close(status.getCode(), status.getReason());

spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketHandlerAdapter.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.util.Assert;
2929
import org.springframework.web.socket.BinaryMessage;
3030
import org.springframework.web.socket.CloseStatus;
31+
import org.springframework.web.socket.PongMessage;
3132
import org.springframework.web.socket.TextMessage;
3233
import org.springframework.web.socket.WebSocketHandler;
3334
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
@@ -89,6 +90,13 @@ public void onMessage(ByteBuffer message) {
8990
});
9091
}
9192

93+
session.addMessageHandler(new MessageHandler.Whole<javax.websocket.PongMessage>() {
94+
@Override
95+
public void onMessage(javax.websocket.PongMessage message) {
96+
handlePongMessage(session, message.getApplicationData());
97+
}
98+
});
99+
92100
try {
93101
this.handler.afterConnectionEstablished(this.wsSession);
94102
}
@@ -118,6 +126,16 @@ private void handleBinaryMessage(javax.websocket.Session session, ByteBuffer pay
118126
}
119127
}
120128

129+
private void handlePongMessage(javax.websocket.Session session, ByteBuffer payload) {
130+
PongMessage pongMessage = new PongMessage(payload);
131+
try {
132+
this.handler.handleMessage(this.wsSession, pongMessage);
133+
}
134+
catch (Throwable t) {
135+
ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger);
136+
}
137+
}
138+
121139
@Override
122140
public void onClose(javax.websocket.Session session, CloseReason reason) {
123141
CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase());

spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSession.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.springframework.util.StringUtils;
3030
import org.springframework.web.socket.BinaryMessage;
3131
import org.springframework.web.socket.CloseStatus;
32+
import org.springframework.web.socket.PingMessage;
33+
import org.springframework.web.socket.PongMessage;
3234
import org.springframework.web.socket.TextMessage;
3335
import org.springframework.web.socket.WebSocketSession;
3436

@@ -121,6 +123,16 @@ protected void sendBinaryMessage(BinaryMessage message) throws IOException {
121123
getNativeSession().getBasicRemote().sendBinary(message.getPayload(), message.isLast());
122124
}
123125

126+
@Override
127+
protected void sendPingMessage(PingMessage message) throws IOException {
128+
getNativeSession().getBasicRemote().sendPing(message.getPayload());
129+
}
130+
131+
@Override
132+
protected void sendPongMessage(PongMessage message) throws IOException {
133+
getNativeSession().getBasicRemote().sendPong(message.getPayload());
134+
}
135+
124136
@Override
125137
protected void closeInternal(CloseStatus status) throws IOException {
126138
getNativeSession().close(new CloseReason(CloseCodes.getCloseCode(status.getCode()), status.getReason()));

spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.springframework.web.socket.BinaryMessage;
2020
import org.springframework.web.socket.CloseStatus;
21+
import org.springframework.web.socket.PongMessage;
2122
import org.springframework.web.socket.TextMessage;
2223
import org.springframework.web.socket.WebSocketHandler;
2324
import org.springframework.web.socket.WebSocketMessage;
@@ -32,6 +33,7 @@
3233
*/
3334
public class WebSocketHandlerAdapter implements WebSocketHandler {
3435

36+
3537
@Override
3638
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
3739
}
@@ -44,6 +46,9 @@ public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
4446
else if (message instanceof BinaryMessage) {
4547
handleBinaryMessage(session, (BinaryMessage) message);
4648
}
49+
else if (message instanceof PongMessage) {
50+
handlePongMessage(session, (PongMessage) message);
51+
}
4752
else {
4853
throw new IllegalStateException("Unexpected WebSocket message type: " + message);
4954
}
@@ -55,6 +60,9 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message)
5560
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
5661
}
5762

63+
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
64+
}
65+
5866
@Override
5967
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
6068
}

0 commit comments

Comments
 (0)