@Test public void sendAfterBlockedSend() throws IOException, InterruptedException { BlockingSession session = new BlockingSession(); session.setOpen(true); final ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 1024); sendBlockingMessage(decorator); Thread.sleep(50); assertTrue(decorator.getTimeSinceSendStarted() > 0); TextMessage payload = new TextMessage("payload"); for (int i = 0; i < 5; i++) { decorator.sendMessage(payload); } assertTrue(decorator.getTimeSinceSendStarted() > 0); assertEquals(5 * payload.getPayloadLength(), decorator.getBufferSize()); assertTrue(session.isOpen()); }
private boolean tryFlushMessageBuffer() throws IOException { if (this.flushLock.tryLock()) { try { while (true) { WebSocketMessage<?> message = this.buffer.poll(); if (message == null || shouldNotSend()) { break; } this.bufferSize.addAndGet(-message.getPayloadLength()); this.sendStartTime = System.currentTimeMillis(); getDelegate().sendMessage(message); this.sendStartTime = 0; } } finally { this.sendStartTime = 0; this.flushLock.unlock(); } return true; } return false; }
@Override public void sendMessage(WebSocketMessage<?> message) throws IOException { if (shouldNotSend()) { return; } this.buffer.add(message); this.bufferSize.addAndGet(message.getPayloadLength()); do { if (!tryFlushMessageBuffer()) { if (logger.isTraceEnabled()) { logger.trace(String.format("Another send already in progress: " + "session id '%s':, \"in-progress\" send time %d (ms), buffer size %d bytes", getId(), getTimeSinceSendStarted(), getBufferSize())); } checkSessionLimits(); break; } } while (!this.buffer.isEmpty() && !shouldNotSend()); }
private void checkSessionLimits() { if (!shouldNotSend() && this.closeLock.tryLock()) { try { if (getTimeSinceSendStarted() > getSendTimeLimit()) { String format = "Send time %d (ms) for session '%s' exceeded the allowed limit %d"; String reason = String.format(format, getTimeSinceSendStarted(), getId(), getSendTimeLimit()); limitExceeded(reason); else if (getBufferSize() > getBufferSizeLimit()) { switch (this.overflowStrategy) { case TERMINATE: String format = "Buffer size %d bytes for session '%s' exceeds the allowed limit %d"; String reason = String.format(format, getBufferSize(), getId(), getBufferSizeLimit()); limitExceeded(reason); break; case DROP: int i = 0; while (getBufferSize() > getBufferSizeLimit()) { WebSocketMessage<?> message = this.buffer.poll(); if (message == null) { logger.debug("Dropped " + i + " messages, buffer size: " + getBufferSize());
@Test public void sendTimeLimitExceeded() throws IOException, InterruptedException { BlockingSession session = new BlockingSession(); session.setId("123"); session.setOpen(true); final ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, 100, 1024); sendBlockingMessage(decorator); // Exceed send time.. Thread.sleep(200); try { TextMessage payload = new TextMessage("payload"); decorator.sendMessage(payload); fail("Expected exception"); } catch (SessionLimitExceededException ex) { String actual = ex.getMessage(); String regex = "Send time [\\d]+ \\(ms\\) for session '123' exceeded the allowed limit 100"; assertTrue("Unexpected message: " + actual, actual.matches(regex)); assertEquals(CloseStatus.SESSION_NOT_RELIABLE, ex.getStatus()); } }
/** * Decorate the given {@link WebSocketSession}, if desired. * <p>The default implementation builds a {@link ConcurrentWebSocketSessionDecorator} * with the configured {@link #getSendTimeLimit() send-time limit} and * {@link #getSendBufferSizeLimit() buffer-size limit}. * @param session the original {@code WebSocketSession} * @return the decorated {@code WebSocketSession}, or potentially the given session as-is * @since 4.3.13 */ protected WebSocketSession decorateSession(WebSocketSession session) { return new ConcurrentWebSocketSessionDecorator(session, getSendTimeLimit(), getSendBufferSizeLimit()); }
@Test // SPR-17140 public void overflowStrategyDrop() throws IOException, InterruptedException { BlockingSession session = new BlockingSession(); session.setId("123"); session.setOpen(true); final ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, 10*1000, 1024, OverflowStrategy.DROP); sendBlockingMessage(decorator); StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < 1023; i++) { sb.append("a"); } for (int i=0; i < 5; i++) { TextMessage message = new TextMessage(sb.toString()); decorator.sendMessage(message); } assertEquals(1023, decorator.getBufferSize()); assertTrue(session.isOpen()); }
@Test public void closeStatusChangesToSessionNotReliable() throws Exception { BlockingSession session = new BlockingSession(); session.setId("123"); session.setOpen(true); CountDownLatch sentMessageLatch = session.getSentMessageLatch(); int sendTimeLimit = 100; int bufferSizeLimit = 1024; final ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, sendTimeLimit, bufferSizeLimit); Executors.newSingleThreadExecutor().submit((Runnable) () -> { TextMessage message = new TextMessage("slow message"); try { decorator.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } }); assertTrue(sentMessageLatch.await(5, TimeUnit.SECONDS)); // ensure some send time elapses Thread.sleep(sendTimeLimit + 100); decorator.close(CloseStatus.PROTOCOL_ERROR); assertEquals("CloseStatus should have changed to SESSION_NOT_RELIABLE", CloseStatus.SESSION_NOT_RELIABLE, session.getCloseStatus()); }
private void sendBlockingMessage(ConcurrentWebSocketSessionDecorator session) throws InterruptedException { Executors.newSingleThreadExecutor().submit(() -> { TextMessage message = new TextMessage("slow message"); try { session.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } }); BlockingSession delegate = (BlockingSession) session.getDelegate(); assertTrue(delegate.getSentMessageLatch().await(5, TimeUnit.SECONDS)); }
@Override public String toString() { return getDelegate().toString(); }
@Override public void close(CloseStatus status) throws IOException { this.closeLock.lock(); try { if (this.closeInProgress) { return; } if (!CloseStatus.SESSION_NOT_RELIABLE.equals(status)) { try { checkSessionLimits(); } catch (SessionLimitExceededException ex) { // Ignore } if (this.limitExceeded) { if (logger.isDebugEnabled()) { logger.debug("Changing close status " + status + " to SESSION_NOT_RELIABLE."); } status = CloseStatus.SESSION_NOT_RELIABLE; } } this.closeInProgress = true; super.close(status); } finally { this.closeLock.unlock(); } }
private void checkSessionLimits() { if (!shouldNotSend() && this.closeLock.tryLock()) { try { if (getTimeSinceSendStarted() > getSendTimeLimit()) { String format = "Message send time %d (ms) for session '%s' exceeded the allowed limit %d"; String reason = String.format(format, getTimeSinceSendStarted(), getId(), getSendTimeLimit()); limitExceeded(reason); } else if (getBufferSize() > getBufferSizeLimit()) { String format = "The send buffer size %d bytes for session '%s' exceeded the allowed limit %d"; String reason = String.format(format, getBufferSize(), getId(), getBufferSizeLimit()); limitExceeded(reason); } } finally { this.closeLock.unlock(); } } }
@Override public void afterConnectionEstablished(WebSocketSession session) throws IOException { String username = (String) session.getAttributes().get("username"); ConcurrentWebSocketSessionDecorator sessionDecorator = new ConcurrentWebSocketSessionDecorator(session, 1000, 5120); sessionMap.put(username, sessionDecorator); log.info("connect succ, username:[{}]", username); JSONObject jsonObject = new JSONObject(); jsonObject.put("action", "connect"); jsonObject.put("username", username); sessionDecorator.sendMessage(new TextMessage(jsonObject.toJSONString())); }
@Test public void closeStatusNormal() throws Exception { BlockingSession session = new BlockingSession(); session.setOpen(true); WebSocketSession decorator = new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 1024); decorator.close(CloseStatus.PROTOCOL_ERROR); assertEquals(CloseStatus.PROTOCOL_ERROR, session.getCloseStatus()); decorator.close(CloseStatus.SERVER_ERROR); assertEquals("Should have been ignored", CloseStatus.PROTOCOL_ERROR, session.getCloseStatus()); }
@Test public void sendBufferSizeExceeded() throws IOException, InterruptedException { BlockingSession session = new BlockingSession(); session.setId("123"); session.setOpen(true); final ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, 10*1000, 1024); sendBlockingMessage(decorator); StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < 1023; i++) { sb.append("a"); } TextMessage message = new TextMessage(sb.toString()); decorator.sendMessage(message); assertEquals(1023, decorator.getBufferSize()); assertTrue(session.isOpen()); try { decorator.sendMessage(message); fail("Expected exception"); } catch (SessionLimitExceededException ex) { String actual = ex.getMessage(); String regex = "Buffer size [\\d]+ bytes for session '123' exceeds the allowed limit 1024"; assertTrue("Unexpected message: " + actual, actual.matches(regex)); assertEquals(CloseStatus.SESSION_NOT_RELIABLE, ex.getStatus()); } }
@Override public String toString() { return getDelegate().toString(); }
@Override public void close(CloseStatus status) throws IOException { this.closeLock.lock(); try { if (this.closeInProgress) { return; } if (!CloseStatus.SESSION_NOT_RELIABLE.equals(status)) { try { checkSessionLimits(); } catch (SessionLimitExceededException ex) { // Ignore } if (this.limitExceeded) { if (logger.isDebugEnabled()) { logger.debug("Changing close status " + status + " to SESSION_NOT_RELIABLE."); } status = CloseStatus.SESSION_NOT_RELIABLE; } } this.closeInProgress = true; super.close(status); } finally { this.closeLock.unlock(); } }
private void checkSessionLimits() { if (!shouldNotSend() && this.closeLock.tryLock()) { try { if (getTimeSinceSendStarted() > getSendTimeLimit()) { String format = "Send time %d (ms) for session '%s' exceeded the allowed limit %d"; String reason = String.format(format, getTimeSinceSendStarted(), getId(), getSendTimeLimit()); limitExceeded(reason); else if (getBufferSize() > getBufferSizeLimit()) { switch (this.overflowStrategy) { case TERMINATE: String format = "Buffer size %d bytes for session '%s' exceeds the allowed limit %d"; String reason = String.format(format, getBufferSize(), getId(), getBufferSizeLimit()); limitExceeded(reason); break; case DROP: int i = 0; while (getBufferSize() > getBufferSizeLimit()) { WebSocketMessage<?> message = this.buffer.poll(); if (message == null) { logger.debug("Dropped " + i + " messages, buffer size: " + getBufferSize());
@Override public void sendMessage(WebSocketMessage<?> message) throws IOException { if (shouldNotSend()) { return; } this.buffer.add(message); this.bufferSize.addAndGet(message.getPayloadLength()); do { if (!tryFlushMessageBuffer()) { if (logger.isTraceEnabled()) { logger.trace(String.format("Another send already in progress: " + "session id '%s':, \"in-progress\" send time %d (ms), buffer size %d bytes", getId(), getTimeSinceSendStarted(), getBufferSize())); } checkSessionLimits(); break; } } while (!this.buffer.isEmpty() && !shouldNotSend()); }
@Test public void send() throws IOException { TestWebSocketSession session = new TestWebSocketSession(); session.setOpen(true); ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, 1000, 1024); TextMessage textMessage = new TextMessage("payload"); decorator.sendMessage(textMessage); assertEquals(1, session.getSentMessages().size()); assertEquals(textMessage, session.getSentMessages().get(0)); assertEquals(0, decorator.getBufferSize()); assertEquals(0, decorator.getTimeSinceSendStarted()); assertTrue(session.isOpen()); }