2525import java .util .concurrent .atomic .AtomicLongFieldUpdater ;
2626import java .util .stream .Stream ;
2727
28+ import javax .websocket .CloseReason ;
29+ import javax .websocket .CloseReason .CloseCode ;
30+ import javax .websocket .CloseReason .CloseCodes ;
2831import javax .websocket .Extension ;
2932import javax .websocket .Session ;
3033
@@ -65,6 +68,7 @@ public class WebSocketConnection extends AttributeStore implements Comparable<We
6568 // associated websocket session
6669 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
@@ -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 ;
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 ;
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 ;
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 ;
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,14 +321,34 @@ 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- // trying to close the session nicely
338+ // no blank reasons
339+ if (reasonPhrase == null ) {
340+ reasonPhrase = "" ;
341+ }
342+ log .debug ("close: {} code: {} reason: {}" , wsSessionId , code , reasonPhrase );
328343 try {
329- wsSession .close ();
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 );
351+ }
330352 } catch (Exception e ) {
331353 log .debug ("Exception closing session" , e );
332354 }
@@ -343,39 +365,9 @@ public void close() {
343365 if (headers != null ) {
344366 headers = null ;
345367 }
346- if (scope .get () != null ) {
347- // disconnect from scope
348- scope .get ().removeConnection (this );
349- // clear weak refs
350- scope .clear ();
351- }
352368 }
353369 }
354370
355- /*
356- WsSession uses these userProperties for checkExpiration along with maxIdleTimeout
357-
358- configuration for read idle timeout on WebSocket session
359- READ_IDLE_TIMEOUT_MS = "org.apache.tomcat.websocket.READ_IDLE_TIMEOUT_MS";
360- configuration for write idle timeout on WebSocket session
361- WRITE_IDLE_TIMEOUT_MS = "org.apache.tomcat.websocket.WRITE_IDLE_TIMEOUT_MS";
362- */
363- public void timeoutAsync (long now ) {
364- // XXX(paul) only logging here as we should more than likely rely upon the container checking expiration
365- log .trace ("timeoutAsync: {} on session id: {} read: {} written: {}" , now , wsSessionId , readBytes , writtenBytes );
366- /*
367- WsSession session = wsSession;
368- Map<String, Object> props = session.getUserProperties();
369- log.debug("Session properties: {}", props);
370- long maxIdleTimeout = session.getMaxIdleTimeout();
371- long readTimeout = (long) props.get(Constants.READ_IDLE_TIMEOUT_MS);
372- long sendTimeout = (long) props.get(Constants.WRITE_IDLE_TIMEOUT_MS);
373- log.debug("Session timeouts - max: {} read: {} write: {}", maxIdleTimeout, readTimeout, sendTimeout);
374- //long readDelta = (now - lastReadTime), writeDelta = (now - lastWriteTime);
375- //log.debug("timeoutAsync: {} on {} last read: {} last write: {}", now, wsSessionId, readDelta, writeDelta);
376- */
377- }
378-
379371 /**
380372 * Async send is enabled in non-Windows based systems; this provides a means to override it.
381373 *
0 commit comments