From a3ba9e5c96fb3165959906fb475d5741337e573d Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Fri, 28 Nov 2025 18:06:40 -0600 Subject: [PATCH] Websocket reliability changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In WsWriteTimeout, ConcurrentSkipListSet ordering collapses endpoints that share the same timeoutExpiry (comparator returns 0 on equal timestamps). Any concurrent async writes that pick the same millisecond expiry will drop all but one endpoint from the timeout set, so those writes never time out and can hang indefinitely. Origin checking in server/DefaultServerEndpointConfigurator.java no longer throws when the Origin header is missing; with cross-origin policy enabled it rejects missing origins cleanly and logs the reason. The connection ID stored in WebSocketConnection is a random string, but the on-close fallback lookup uses the container session ID. When the session user properties can’t be read (the scenario the fallback is meant for), the lookup always fails and the connection stays registered in the scope/manager, leaking resources and skipping disconnect notifications. --- .../org/red5/net/websocket/WebSocketConnection.java | 6 ++---- .../server/DefaultServerEndpointConfigurator.java | 4 ++++ .../org/red5/net/websocket/server/WsWriteTimeout.java | 10 ++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/red5/net/websocket/WebSocketConnection.java b/server/src/main/java/org/red5/net/websocket/WebSocketConnection.java index b1fabcb0f..c58e47dd8 100644 --- a/server/src/main/java/org/red5/net/websocket/WebSocketConnection.java +++ b/server/src/main/java/org/red5/net/websocket/WebSocketConnection.java @@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.stream.Stream; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.tomcat.websocket.Constants; import org.apache.tomcat.websocket.WsSession; @@ -128,9 +127,8 @@ public WebSocketConnection(WebSocketScope scope, Session session) { if (isDebug) { log.debug("ws session: {}", wsSession); } - // the websocket session id will be used for hash code comparison, its the only usable value currently - //wsSessionId = session.getId(); - wsSessionId = RandomStringUtils.insecure().nextAlphabetic(11); // random 11 char string + // use the websocket session id for comparisons and lookups + wsSessionId = session.getId(); hashCode = wsSessionId.hashCode(); log.info("ws id: {} hashCode: {}", wsSessionId, hashCode); // get extensions diff --git a/server/src/main/java/org/red5/net/websocket/server/DefaultServerEndpointConfigurator.java b/server/src/main/java/org/red5/net/websocket/server/DefaultServerEndpointConfigurator.java index c93ac11c0..b25ea163b 100644 --- a/server/src/main/java/org/red5/net/websocket/server/DefaultServerEndpointConfigurator.java +++ b/server/src/main/java/org/red5/net/websocket/server/DefaultServerEndpointConfigurator.java @@ -101,6 +101,10 @@ public boolean checkOrigin(String originHeaderValue) { log.debug("checkOrigin: {}", originHeaderValue); // if CORS is enabled if (crossOriginPolicy) { + if (originHeaderValue == null) { + log.info("Origin header is missing and cross-origin policy is enabled"); + return false; + } log.debug("allowedOrigins: {}", Arrays.toString(allowedOrigins)); // allow "*" == any / all or origin suffix matches Optional opt = Stream.of(allowedOrigins).filter(origin -> "*".equals(origin) || origin.endsWith(originHeaderValue)).findFirst(); diff --git a/server/src/main/java/org/red5/net/websocket/server/WsWriteTimeout.java b/server/src/main/java/org/red5/net/websocket/server/WsWriteTimeout.java index 15d5eea0a..50ce660da 100644 --- a/server/src/main/java/org/red5/net/websocket/server/WsWriteTimeout.java +++ b/server/src/main/java/org/red5/net/websocket/server/WsWriteTimeout.java @@ -100,13 +100,11 @@ private static class EndpointComparator implements Comparator