2019. február 28., csütörtök

Custom exception by Websocket Connection


Recently I needed to implement a custom check for WebSocket connection of a Spring Boot application. The logic had to check if a configured connection limit has been reached, and reject opening further connections if so.

The connections were opened by a mobile application client, and the developer wanted to get the error message in a JSON form, in order to parse it easily to an error object.

The Spring framework makes it possible to define interceptors for the Websocket communication. In my case the interceptor looked like this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
final class NumberOfConnectedClientsChannelInterceptor implements ChannelInterceptor {
 private static final Logger log = LoggerFactory.getLogger(NumberOfConnectedClientsChannelInterceptor.class);

 private final SocketServiceConfiguration socketServiceConfiguration;

 private final LifeSocketConnectionRepository lifeSocketConnectionDao;

 public NumberOfConnectedClientsChannelInterceptor(SocketServiceConfiguration socketServiceConfiguration,
   LifeSocketConnectionRepository lifeSocketConnectionDao) {

  this.socketServiceConfiguration = socketServiceConfiguration;
  this.lifeSocketConnectionDao = lifeSocketConnectionDao;
 }

 @Override
 public Message<?> preSend(Message<?> message, MessageChannel channel) {
  StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
  checkConnectionLimit(accessor);

  return message;
 }

 private void checkConnectionLimit(StompHeaderAccessor accessor) {
  if (StompCommand.CONNECT.equals(accessor.getCommand())) {

   long alreadyConnectedClients = lifeSocketConnectionDao.count();

   log.info("Checking connection limit. Connection limit: {} Number of connections: {} ",
     socketServiceConfiguration.getMaxNumberOfConnections(), alreadyConnectedClients);

   if (alreadyConnectedClients >= socketServiceConfiguration.getMaxNumberOfConnections()) {
    log.warn(
      "Too many connected clients. Connecion refused. Connection limit: {} Number of connections: {}",
      socketServiceConfiguration.getMaxNumberOfConnections(), alreadyConnectedClients);
    throw WebsocketConnectionLimitException.ofConnectionLimitError();
   }
  }
 }
}

The interceptor need to be registered in the Websocket configuration class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

...
 @Override
 public void configureClientInboundChannel(ChannelRegistration registration) {
  registration.interceptors(
   new NumberOfConnectedClientsChannelInterceptor(socketServiceConfiguration, lifeSocketConnectionDao));
 }

}

As you can see, the interceptor can only throw an exception to indicate the problem. The solution is a custom exception, extending MessagingException.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import org.springframework.messaging.MessagingException;

public class WebsocketConnectionLimitException extends MessagingException {

 private static final String CONNECTION_LIMIT_MESSAGE = "{\"errorCode\": 403, \"errorMessage\":\"Too many connected clients\"}";
 
 public WebsocketConnectionLimitException(String message) {
  super(CONNECTION_LIMIT_MESSAGE);
 }
}

In case of any other exception type, the Spring framework returns the String representation of the exception, instead of the message itself.

According to the Stomp specification, the client must be able to escape some special characters.
  • \r (octet 92 and 114) translates to carriage return (octet 13)
  • \n (octet 92 and 110) translates to line feed (octet 10)
  • \c (octet 92 and 99) translates to : (octet 58)
  • \\ (octet 92 and 92) translates to \ (octet 92)

While in JSON the ":" character is used quite commonly, the client will have problem with deserialization if it can not handle the escaping out of box.

For more details see http://stomp.github.io/stomp-specification-1.2.html#Value_Encoding