Skip to content

Commit 268ccb6

Browse files
committed
SubProtocolWebSocketHandler provides protected decorateSession method
Issue: SPR-16089
1 parent 9c7141a commit 268ccb6

File tree

3 files changed

+82
-47
lines changed

3 files changed

+82
-47
lines changed

spring-websocket/src/main/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.java

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,13 +34,13 @@
3434
* Wrap a {@link org.springframework.web.socket.WebSocketSession WebSocketSession}
3535
* to guarantee only one thread can send messages at a time.
3636
*
37-
* <p>If a send is slow, subsequent attempts to send more messages from other
38-
* threads will not be able to acquire the flush lock and messages will be
39-
* buffered instead -- at that time, the specified buffer-size limit and
40-
* send-time limit will be checked and the session closed if the limits are
41-
* exceeded.
37+
* <p>If a send is slow, subsequent attempts to send more messages from other threads
38+
* will not be able to acquire the flush lock and messages will be buffered instead.
39+
* At that time, the specified buffer-size limit and send-time limit will be checked
40+
* and the session will be closed if the limits are exceeded.
4241
*
4342
* @author Rossen Stoyanchev
43+
* @author Juergen Hoeller
4444
* @since 4.0.3
4545
*/
4646
public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorator {
@@ -52,7 +52,6 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
5252

5353
private final int bufferSizeLimit;
5454

55-
5655
private final Queue<WebSocketMessage<?>> buffer = new LinkedBlockingQueue<WebSocketMessage<?>>();
5756

5857
private final AtomicInteger bufferSize = new AtomicInteger();
@@ -63,7 +62,6 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
6362

6463
private volatile boolean closeInProgress;
6564

66-
6765
private final Lock flushLock = new ReentrantLock();
6866

6967
private final Lock closeLock = new ReentrantLock();
@@ -82,10 +80,33 @@ public ConcurrentWebSocketSessionDecorator(WebSocketSession delegate, int sendTi
8280
}
8381

8482

83+
/**
84+
* Return the configured send-time limit (milliseconds).
85+
* @since 4.3.13
86+
*/
87+
public int getSendTimeLimit() {
88+
return this.sendTimeLimit;
89+
}
90+
91+
/**
92+
* Return the configured buffer-size limit (number of bytes).
93+
* @since 4.3.13
94+
*/
95+
public int getBufferSizeLimit() {
96+
return this.bufferSizeLimit;
97+
}
98+
99+
/**
100+
* Return the current buffer size (number of bytes).
101+
*/
85102
public int getBufferSize() {
86103
return this.bufferSize.get();
87104
}
88105

106+
/**
107+
* Return the time (milliseconds) since the current send started,
108+
* or 0 if no send is currently in progress.
109+
*/
89110
public long getTimeSinceSendStarted() {
90111
long start = this.sendStartTime;
91112
return (start > 0 ? (System.currentTimeMillis() - start) : 0);
@@ -105,7 +126,7 @@ public void sendMessage(WebSocketMessage<?> message) throws IOException {
105126
if (logger.isTraceEnabled()) {
106127
String text = String.format("Another send already in progress: " +
107128
"session id '%s':, \"in-progress\" send time %d (ms), buffer size %d bytes",
108-
getId(), getTimeSinceSendStarted(), this.bufferSize.get());
129+
getId(), getTimeSinceSendStarted(), getBufferSize());
109130
logger.trace(text);
110131
}
111132
checkSessionLimits();
@@ -142,18 +163,18 @@ private boolean tryFlushMessageBuffer() throws IOException {
142163
return false;
143164
}
144165

145-
private void checkSessionLimits() throws IOException {
166+
private void checkSessionLimits() {
146167
if (!shouldNotSend() && this.closeLock.tryLock()) {
147168
try {
148-
if (getTimeSinceSendStarted() > this.sendTimeLimit) {
169+
if (getTimeSinceSendStarted() > getSendTimeLimit()) {
149170
String format = "Message send time %d (ms) for session '%s' exceeded the allowed limit %d";
150-
String reason = String.format(format, getTimeSinceSendStarted(), getId(), this.sendTimeLimit);
151-
setLimitExceeded(reason);
171+
String reason = String.format(format, getTimeSinceSendStarted(), getId(), getSendTimeLimit());
172+
limitExceeded(reason);
152173
}
153-
else if (this.bufferSize.get() > this.bufferSizeLimit) {
174+
else if (getBufferSize() > getBufferSizeLimit()) {
154175
String format = "The send buffer size %d bytes for session '%s' exceeded the allowed limit %d";
155-
String reason = String.format(format, this.bufferSize.get(), getId(), this.bufferSizeLimit);
156-
setLimitExceeded(reason);
176+
String reason = String.format(format, getBufferSize(), getId(), getBufferSizeLimit());
177+
limitExceeded(reason);
157178
}
158179
}
159180
finally {
@@ -162,7 +183,7 @@ else if (this.bufferSize.get() > this.bufferSizeLimit) {
162183
}
163184
}
164185

165-
private void setLimitExceeded(String reason) {
186+
private void limitExceeded(String reason) {
166187
this.limitExceeded = true;
167188
throw new SessionLimitExceededException(reason, CloseStatus.SESSION_NOT_RELIABLE);
168189
}

spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolHandler.java

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,24 +25,23 @@
2525
import org.springframework.web.socket.WebSocketSession;
2626

2727
/**
28-
* A contract for handling WebSocket messages as part of a higher level protocol, referred
29-
* to as "sub-protocol" in the WebSocket RFC specification. Handles both
28+
* A contract for handling WebSocket messages as part of a higher level protocol,
29+
* referred to as "sub-protocol" in the WebSocket RFC specification. Handles both
3030
* {@link WebSocketMessage}s from a client as well as {@link Message}s to a client.
31-
* <p>
32-
* Implementations of this interface can be configured on a
33-
* {@link SubProtocolWebSocketHandler} which selects a sub-protocol handler to delegate
34-
* messages to based on the sub-protocol requested by the client through the
35-
* {@code Sec-WebSocket-Protocol} request header.
31+
*
32+
* <p>Implementations of this interface can be configured on a
33+
* {@link SubProtocolWebSocketHandler} which selects a sub-protocol handler to
34+
* delegate messages to based on the sub-protocol requested by the client through
35+
* the {@code Sec-WebSocket-Protocol} request header.
3636
*
3737
* @author Andy Wilkinson
3838
* @author Rossen Stoyanchev
39-
*
4039
* @since 4.0
4140
*/
4241
public interface SubProtocolHandler {
4342

4443
/**
45-
* Return the list of sub-protocols supported by this handler, never {@code null}.
44+
* Return the list of sub-protocols supported by this handler (never {@code null}).
4645
*/
4746
List<String> getSupportedProtocols();
4847

@@ -52,12 +51,11 @@ public interface SubProtocolHandler {
5251
* @param message the client message
5352
* @param outputChannel an output channel to send messages to
5453
*/
55-
void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> message,
56-
MessageChannel outputChannel) throws Exception;
54+
void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> message, MessageChannel outputChannel)
55+
throws Exception;
5756

5857
/**
59-
* Handle the given {@link Message} to the client associated with the given WebSocket
60-
* session.
58+
* Handle the given {@link Message} to the client associated with the given WebSocket session.
6159
* @param session the client session
6260
* @param message the client message
6361
*/
@@ -82,7 +80,7 @@ void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> messa
8280
* @param closeStatus the reason why the session was closed
8381
* @param outputChannel a channel
8482
*/
85-
void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus,
86-
MessageChannel outputChannel) throws Exception;
83+
void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus, MessageChannel outputChannel)
84+
throws Exception;
8785

8886
}

spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.web.socket.messaging;
1818

19-
import java.io.IOException;
2019
import java.util.ArrayList;
2120
import java.util.Collections;
2221
import java.util.LinkedHashSet;
@@ -289,7 +288,7 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio
289288
return;
290289
}
291290
this.stats.incrementSessionCount(session);
292-
session = new ConcurrentWebSocketSessionDecorator(session, getSendTimeLimit(), getSendBufferSizeLimit());
291+
session = decorateSession(session);
293292
this.sessions.put(session.getId(), new WebSocketSessionHolder(session));
294293
findProtocolHandler(session).afterSessionStarted(session, this.clientInboundChannel);
295294
}
@@ -374,6 +373,23 @@ public boolean supportsPartialMessages() {
374373
}
375374

376375

376+
/**
377+
* Decorate the given {@link WebSocketSession}, if desired.
378+
* <p>The default implementation builds a {@link ConcurrentWebSocketSessionDecorator}
379+
* with the configured {@link #getSendTimeLimit() send-time limit} and
380+
* {@link #getSendBufferSizeLimit() buffer-size limit}.
381+
* @param session the original {@code WebSocketSession}
382+
* @return the decorated {@code WebSocketSession}, or potentially the given session as-is
383+
* @since 4.3.13
384+
*/
385+
protected WebSocketSession decorateSession(WebSocketSession session) {
386+
return new ConcurrentWebSocketSessionDecorator(session, getSendTimeLimit(), getSendBufferSizeLimit());
387+
}
388+
389+
/**
390+
* Find a {@link SubProtocolHandler} for the given session.
391+
* @param session the {@code WebSocketSession} to find a handler for
392+
*/
377393
protected final SubProtocolHandler findProtocolHandler(WebSocketSession session) {
378394
String protocol = null;
379395
try {
@@ -428,12 +444,11 @@ private String resolveSessionId(Message<?> message) {
428444
* When a session is connected through a higher-level protocol it has a chance
429445
* to use heartbeat management to shut down sessions that are too slow to send
430446
* or receive messages. However, after a WebSocketSession is established and
431-
* before the higher level protocol is fully connected there is a possibility
432-
* for sessions to hang. This method checks and closes any sessions that have
433-
* been connected for more than 60 seconds without having received a single
434-
* message.
447+
* before the higher level protocol is fully connected there is a possibility for
448+
* sessions to hang. This method checks and closes any sessions that have been
449+
* connected for more than 60 seconds without having received a single message.
435450
*/
436-
private void checkSessions() throws IOException {
451+
private void checkSessions() {
437452
long currentTime = System.currentTimeMillis();
438453
if (!isRunning() || (currentTime - this.lastSessionCheckTime < TIME_TO_FIRST_MESSAGE)) {
439454
return;
@@ -493,12 +508,13 @@ private static class WebSocketSessionHolder {
493508

494509
private final WebSocketSession session;
495510

496-
private final long createTime = System.currentTimeMillis();
511+
private final long createTime;
497512

498-
private volatile boolean handledMessages;
513+
private volatile boolean hasHandledMessages;
499514

500-
private WebSocketSessionHolder(WebSocketSession session) {
515+
public WebSocketSessionHolder(WebSocketSession session) {
501516
this.session = session;
517+
this.createTime = System.currentTimeMillis();
502518
}
503519

504520
public WebSocketSession getSession() {
@@ -510,17 +526,17 @@ public long getCreateTime() {
510526
}
511527

512528
public void setHasHandledMessages() {
513-
this.handledMessages = true;
529+
this.hasHandledMessages = true;
514530
}
515531

516532
public boolean hasHandledMessages() {
517-
return this.handledMessages;
533+
return this.hasHandledMessages;
518534
}
519535

520536
@Override
521537
public String toString() {
522538
return "WebSocketSessionHolder[session=" + this.session + ", createTime=" +
523-
this.createTime + ", hasHandledMessages=" + this.handledMessages + "]";
539+
this.createTime + ", hasHandledMessages=" + this.hasHandledMessages + "]";
524540
}
525541
}
526542

0 commit comments

Comments
 (0)