2525import java .util .concurrent .atomic .AtomicLongFieldUpdater ;
2626import java .util .stream .Stream ;
2727
28- import jakarta .websocket .Extension ;
29- import jakarta .websocket .Session ;
30-
3128import org .apache .commons .lang3 .StringUtils ;
3229import org .apache .tomcat .websocket .Constants ;
3330import org .apache .tomcat .websocket .WsSession ;
3431import org .red5 .server .AttributeStore ;
3532import org .slf4j .Logger ;
3633import 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 () {
0 commit comments