Skip to content

Commit b4f3afd

Browse files
committed
Update with WS fixes from master
1 parent 97d2c6c commit b4f3afd

9 files changed

Lines changed: 133 additions & 130 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ Subreddit: [r/red5](http://www.reddit.com/r/red5)
2121

2222
Automatic builds (Courtesy of Apache [OpenMeetings](http://openmeetings.apache.org/)):
2323

24-
* [Red5](https://builds.apache.org/view/M-R/view/OpenMeetings/job/Red5-server/)
25-
* [Windows Installer](https://builds.apache.org/view/M-R/view/OpenMeetings/job/red5-installer/)
24+
* [Red5](https://ci-builds.apache.org/job/OpenMeetings/job/Red5-server/)
25+
* [Windows Installer](https://ci-builds.apache.org/job/OpenMeetings/job/red5-installer/)
2626

2727
__Note on Bootstrap__ The bootstrap and shutdown classes have been moved to the [red5-service](https://github.com/Red5/red5-service) project; the dependency has been added to this projects pom.
2828

io/src/main/java/org/red5/io/utils/TlsUtils.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
public class TlsUtils {
2424

2525
@SuppressWarnings("unused")
26-
private static byte[] DOWNGRADE_TLS11 = Hex.decodeStrict("444F574E47524400");
27-
28-
@SuppressWarnings("unused")
29-
private static byte[] DOWNGRADE_TLS12 = Hex.decodeStrict("444F574E47524401");
26+
private static byte[] DOWNGRADE_TLS11 = Hex.decodeStrict("444F574E47524400"), DOWNGRADE_TLS12 = Hex.decodeStrict("444F574E47524401");
3027

3128
public static final byte[] EMPTY_BYTES = new byte[0];
3229

@@ -610,9 +607,7 @@ public static int[] readUint16Array(int count, InputStream input) throws IOExcep
610607
}
611608

612609
public static ASN1Primitive readASN1Object(byte[] encoding) throws IOException {
613-
ASN1InputStream asn1 = null;
614-
try {
615-
asn1 = new ASN1InputStream(encoding);
610+
try (ASN1InputStream asn1 = new ASN1InputStream(encoding)) {
616611
ASN1Primitive result = asn1.readObject();
617612
if (null == result) {
618613
throw new IOException("AlertDescription.decode_error");
@@ -621,10 +616,6 @@ public static ASN1Primitive readASN1Object(byte[] encoding) throws IOException {
621616
throw new IOException("AlertDescription.decode_error");
622617
}
623618
return result;
624-
} finally {
625-
if (asn1 != null) {
626-
asn1.close();
627-
}
628619
}
629620
}
630621

server/src/main/java/org/red5/net/websocket/WebSocketConnection.java

Lines changed: 52 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@
2525
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
2626
import java.util.stream.Stream;
2727

28-
import jakarta.websocket.Extension;
29-
import jakarta.websocket.Session;
30-
3128
import org.apache.commons.lang3.StringUtils;
3229
import org.apache.tomcat.websocket.Constants;
3330
import org.apache.tomcat.websocket.WsSession;
3431
import org.red5.server.AttributeStore;
3532
import org.slf4j.Logger;
3633
import org.slf4j.LoggerFactory;
3734

35+
import jakarta.websocket.CloseReason;
36+
import jakarta.websocket.CloseReason.CloseCode;
37+
import jakarta.websocket.CloseReason.CloseCodes;
38+
import jakarta.websocket.Extension;
39+
import jakarta.websocket.Session;
40+
3841
/**
3942
* WebSocketConnection <br>
4043
* This class represents a WebSocket connection with a client (browser).
@@ -63,8 +66,9 @@ public class WebSocketConnection extends AttributeStore implements Comparable<We
6366
private AtomicBoolean connected = new AtomicBoolean(false);
6467

6568
// associated websocket session
66-
private WeakReference<WsSession> wsSession;
69+
private final WsSession wsSession;
6770

71+
// reference to the scope for manager access
6872
private WeakReference<WebSocketScope> scope;
6973

7074
// unique identifier for the session
@@ -114,9 +118,9 @@ public WebSocketConnection(WebSocketScope scope, Session session) {
114118
log.debug("path: {}", path);
115119
}
116120
// cast ws session
117-
this.wsSession = new WeakReference<>((WsSession) session);
121+
this.wsSession = (WsSession) session;
118122
if (isDebug) {
119-
log.debug("ws session: {}", wsSession.get());
123+
log.debug("ws session: {}", wsSession);
120124
}
121125
// the websocket session id will be used for hash code comparison, its the only usable value currently
122126
wsSessionId = session.getId();
@@ -163,6 +167,8 @@ public WebSocketConnection(WebSocketScope scope, Session session) {
163167
// add the timeouts to the user props
164168
userProps.put(Constants.READ_IDLE_TIMEOUT_MS, readTimeout);
165169
userProps.put(Constants.WRITE_IDLE_TIMEOUT_MS, sendTimeout);
170+
// set the close timeout to 5 seconds
171+
userProps.put(Constants.SESSION_CLOSE_TIMEOUT_PROPERTY, TimeUnit.SECONDS.toMillis(5));
166172
if (isDebug) {
167173
log.debug("userProps: {}", userProps);
168174
}
@@ -186,9 +192,8 @@ public void send(String data) throws UnsupportedEncodingException, IOException {
186192
}
187193
// process the incoming string
188194
if (StringUtils.isNotBlank(data)) {
189-
final WsSession session = wsSession.get();
190195
// attempt send only if the session is not closed
191-
if (session != null && !session.isClosed()) {
196+
if (!wsSession.isClosed()) {
192197
try {
193198
if (useAsync) {
194199
if (sendFuture != null && !sendFuture.isDone()) {
@@ -197,21 +202,21 @@ public void send(String data) throws UnsupportedEncodingException, IOException {
197202
} catch (TimeoutException e) {
198203
log.warn("Send timed out {}", wsSessionId);
199204
// if the session is not open, cancel the future
200-
if (!session.isOpen()) {
205+
if (!wsSession.isOpen()) {
201206
sendFuture.cancel(true);
202207
return;
203208
}
204209
}
205210
}
206211
synchronized (wsSessionId) {
207212
int lengthToWrite = data.getBytes().length;
208-
sendFuture = session.getAsyncRemote().sendText(data);
213+
sendFuture = wsSession.getAsyncRemote().sendText(data);
209214
updateWriteBytes(lengthToWrite);
210215
}
211216
} else {
212217
synchronized (wsSessionId) {
213218
int lengthToWrite = data.getBytes().length;
214-
session.getBasicRemote().sendText(data);
219+
wsSession.getBasicRemote().sendText(data);
215220
updateWriteBytes(lengthToWrite);
216221
}
217222
}
@@ -236,8 +241,7 @@ public void send(byte[] buf) throws IOException {
236241
if (isDebug) {
237242
log.debug("send binary: {}", Arrays.toString(buf));
238243
}
239-
WsSession session = wsSession.get();
240-
if (session != null && session.isOpen()) {
244+
if (!wsSession.isClosed()) {
241245
try {
242246
// send the bytes
243247
if (useAsync) {
@@ -253,12 +257,12 @@ public void send(byte[] buf) throws IOException {
253257
}
254258
}
255259
synchronized (wsSessionId) {
256-
sendFuture = session.getAsyncRemote().sendBinary(ByteBuffer.wrap(buf));
260+
sendFuture = wsSession.getAsyncRemote().sendBinary(ByteBuffer.wrap(buf));
257261
updateWriteBytes(buf.length);
258262
}
259263
} else {
260264
synchronized (wsSessionId) {
261-
session.getBasicRemote().sendBinary(ByteBuffer.wrap(buf));
265+
wsSession.getBasicRemote().sendBinary(ByteBuffer.wrap(buf));
262266
updateWriteBytes(buf.length);
263267
}
264268
}
@@ -281,11 +285,10 @@ public void sendPing(byte[] buf) throws IllegalArgumentException, IOException {
281285
if (isTrace) {
282286
log.trace("send ping: {}", buf);
283287
}
284-
WsSession session = wsSession.get();
285-
if (session != null && session.isOpen()) {
288+
if (!wsSession.isClosed()) {
286289
synchronized (wsSessionId) {
287290
// send the bytes
288-
session.getBasicRemote().sendPing(ByteBuffer.wrap(buf));
291+
wsSession.getBasicRemote().sendPing(ByteBuffer.wrap(buf));
289292
// update counter
290293
updateWriteBytes(buf.length);
291294
}
@@ -305,11 +308,10 @@ public void sendPong(byte[] buf) throws IllegalArgumentException, IOException {
305308
if (isTrace) {
306309
log.trace("send pong: {}", buf);
307310
}
308-
WsSession session = wsSession.get();
309-
if (session != null && session.isOpen()) {
311+
if (!wsSession.isClosed()) {
310312
synchronized (wsSessionId) {
311313
// send the bytes
312-
session.getBasicRemote().sendPong(ByteBuffer.wrap(buf));
314+
wsSession.getBasicRemote().sendPong(ByteBuffer.wrap(buf));
313315
// update counter
314316
updateWriteBytes(buf.length);
315317
}
@@ -319,20 +321,36 @@ public void sendPong(byte[] buf) throws IllegalArgumentException, IOException {
319321
}
320322

321323
/**
322-
* close Connection
324+
* Close the connection.
323325
*/
324326
public void close() {
327+
close(CloseCodes.NORMAL_CLOSURE, "");
328+
}
329+
330+
/**
331+
* Close the connection with a reason.
332+
*
333+
* @param code CloseCode
334+
* @param reasonPhrase short reason for closing
335+
*/
336+
public void close(CloseCode code, String reasonPhrase) {
325337
if (connected.compareAndSet(true, false)) {
326-
log.debug("close: {}", wsSessionId);
327-
WsSession session = wsSession != null ? wsSession.get() : null;
328-
// session has to be open, or user props cannot be retrieved
329-
if (session != null && session.isOpen()) {
330-
// trying to close the session nicely
331-
try {
332-
session.close();
333-
} catch (Exception e) {
334-
log.debug("Exception closing session", e);
338+
// no blank reasons
339+
if (reasonPhrase == null) {
340+
reasonPhrase = "";
341+
}
342+
log.debug("close: {} code: {} reason: {}", wsSessionId, code, reasonPhrase);
343+
try {
344+
// close the session if open
345+
if (wsSession.isOpen()) {
346+
CloseReason reason = new CloseReason(code, reasonPhrase);
347+
if (isDebug) {
348+
log.debug("Closing session: {} with reason: {}", wsSessionId, reason);
349+
}
350+
wsSession.close(reason);
335351
}
352+
} catch (Exception e) {
353+
log.debug("Exception closing session", e);
336354
}
337355
// clean up our props
338356
attributes.clear();
@@ -347,40 +365,9 @@ public void close() {
347365
if (headers != null) {
348366
headers = null;
349367
}
350-
if (scope.get() != null) {
351-
// disconnect from scope
352-
scope.get().removeConnection(this);
353-
// clear weak refs
354-
wsSession.clear();
355-
scope.clear();
356-
}
357368
}
358369
}
359370

360-
/*
361-
WsSession uses these userProperties for checkExpiration along with maxIdleTimeout
362-
363-
configuration for read idle timeout on WebSocket session
364-
READ_IDLE_TIMEOUT_MS = "org.apache.tomcat.websocket.READ_IDLE_TIMEOUT_MS";
365-
configuration for write idle timeout on WebSocket session
366-
WRITE_IDLE_TIMEOUT_MS = "org.apache.tomcat.websocket.WRITE_IDLE_TIMEOUT_MS";
367-
*/
368-
public void timeoutAsync(long now) {
369-
// XXX(paul) only logging here as we should more than likely rely upon the container checking expiration
370-
log.trace("timeoutAsync: {} on session id: {} read: {} written: {}", now, wsSessionId, readBytes, writtenBytes);
371-
/*
372-
WsSession session = wsSession.get();
373-
Map<String, Object> props = session.getUserProperties();
374-
log.debug("Session properties: {}", props);
375-
long maxIdleTimeout = session.getMaxIdleTimeout();
376-
long readTimeout = (long) props.get(Constants.READ_IDLE_TIMEOUT_MS);
377-
long sendTimeout = (long) props.get(Constants.WRITE_IDLE_TIMEOUT_MS);
378-
log.debug("Session timeouts - max: {} read: {} write: {}", maxIdleTimeout, readTimeout, sendTimeout);
379-
//long readDelta = (now - lastReadTime), writeDelta = (now - lastWriteTime);
380-
//log.debug("timeoutAsync: {} on {} last read: {} last write: {}", now, wsSessionId, readDelta, writeDelta);
381-
*/
382-
}
383-
384371
/**
385372
* Async send is enabled in non-Windows based systems; this provides a means to override it.
386373
*
@@ -453,7 +440,7 @@ public void setOrigin(String origin) {
453440
* @return true if secure and false if unsecure or unconnected
454441
*/
455442
public boolean isSecure() {
456-
Optional<WsSession> opt = Optional.ofNullable(wsSession.get());
443+
Optional<WsSession> opt = Optional.ofNullable(wsSession);
457444
if (opt.isPresent()) {
458445
return (opt.get().isOpen() ? opt.get().isSecure() : false);
459446
}
@@ -672,12 +659,12 @@ public Object getUserProperty(String key) {
672659

673660
public void setWsSessionTimeout(long idleTimeout) {
674661
if (wsSession != null) {
675-
wsSession.get().setMaxIdleTimeout(idleTimeout);
662+
wsSession.setMaxIdleTimeout(idleTimeout);
676663
}
677664
}
678665

679666
public WsSession getWsSession() {
680-
return wsSession != null ? wsSession.get() : null;
667+
return wsSession != null ? wsSession : null;
681668
}
682669

683670
public long getReadBytes() {

server/src/main/java/org/red5/net/websocket/WebSocketScope.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.springframework.beans.factory.DisposableBean;
2525
import org.springframework.beans.factory.InitializingBean;
2626

27+
import jakarta.websocket.CloseReason.CloseCodes;
28+
2729
/**
2830
* WebSocketScope contains an IScope and keeps track of WebSocketConnection and IWebSocketDataListener instances.
2931
*
@@ -91,7 +93,7 @@ public void unregister() {
9193
// clean up the connections by first closing them
9294
conns.forEach(conn -> {
9395
if (conns.remove(conn)) {
94-
conn.close();
96+
conn.close(CloseCodes.GOING_AWAY, "WebSocket scope removed");
9597
}
9698
});
9799
// clean up the listeners by first stopping them

server/src/main/java/org/red5/net/websocket/WebSocketScopeManager.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.slf4j.Logger;
2525
import org.slf4j.LoggerFactory;
2626

27+
import jakarta.websocket.CloseReason.CloseCodes;
28+
2729
/**
2830
* Manages websocket scopes and listeners.
2931
*
@@ -172,15 +174,12 @@ public boolean addWebSocketScope(WebSocketScope webSocketScope) {
172174
wsConn.sendPing(PING_BYTES);
173175
} catch (Exception e) {
174176
log.debug("Exception pinging connection: {} connection will be closed", wsConn.getSessionId(), e);
175-
// if the connection isn't connected, remove them
176-
wsScope.removeConnection(wsConn);
177-
// if the ping fails, consider them gone
178-
wsConn.close();
177+
wsConn.close(CloseCodes.CLOSED_ABNORMALLY, e.getMessage());
179178
}
180179
} else {
181180
log.debug("Removing unconnected connection: {} during ping loop", wsConn.getSessionId());
182181
// if the connection isn't connected, remove them
183-
wsScope.removeConnection(wsConn);
182+
wsConn.close(CloseCodes.UNEXPECTED_CONDITION, "Connection not connected");
184183
}
185184
} catch (Exception e) {
186185
log.warn("Exception in WS pinger", e);

0 commit comments

Comments
 (0)