In the previous section, we saw a WebSocket application of the multiple subscriber model, in which a broker sent messages to a topic. Since all clients had subscribed to the same topic, all of them received messages. Now, you are asked to develop an application that targets a specific user in a WebSocket chat application.
Suppose you want to develop an automated answering application in which a user sends a question to the system and gets an answer automatically. The application is almost the same as the previous one (STOMP over WebSocket and the fallback option in Spring 4), except that we should change the WebSocket configurer and endpoint on the server side and subscription on the client side. The code for the AutoAnsweringWebSocketMessageBrokerConfigurer
class is:
@Configuration @EnableWebSocketMessageBroker public class AutoAnsweringWebSocketMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.setApplicationDestinationPrefixes("/app"); config.enableSimpleBroker("/queue"); config.setUserDestinationPrefix("/user"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/message").withSockJS(); } }
The config.setUserDestinationPrefix("/user")
method sets a prefix noting that a user has subscribed and expects to get their own message on the topic. The code for the AutoAnsweringController
class is:
@Controller public class AutoAnsweringController { @Autowired AutoAnsweringService autoAnsweringService; @MessageMapping("/message") @SendToUser public String sendMessage(ClientInfoBean message) { return autoAnsweringService.answer(message); } @MessageExceptionHandler @SendToUser(value = "/queue/errors", broadcast = false) String handleException(Exception e) { return "caught ${e.message}"; } } @Service public class AutoAnsweringServiceImpl implements AutoAnsweringService { @Override public String answer(ClientInfoBean bean) { StringBuilder mockBuffer=new StringBuilder(); mockBuffer.append(bean.getClientName()) .append(", we have received the message:") .append(bean.getClientMessage()); return mockBuffer.toString(); } }
In the endpoint, we use @SendToUser
instead of @SendTo("...")
. This forwards the response only to the sender of the message. @MessageExceptionHandler
will send errors (broadcast = false)
to the sender of message as well.
AutoAnsweringService
is just a mock service to return an answer to the client message. On the client side, we only add the /user
prefix when a user subscribes to the topic (/user/queue/message
):
function connectService() { var servicePath='/message'; var socket = new SockJS(servicePath); stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setIsJoined(true); stompClient.subscribe('/user/queue/message', function(message) { renderServerReturnedData(message.body); }); stompClient.subscribe('/user/queue/error', function(message) { renderReturnedError(message.body); }); }); } function sendMyClientMessage() { var serviceFullPath='/app/message'; var myText = document.getElementById('myText').value; stompClient.send(serviceFullPath, {}, JSON.stringify({ 'clientName': 'Client-'+randomnumber, 'clientMessage':myText})); document.getElementById('myText').value=''; }
The topic user/queue/error
is used to receive errors dispatched from the server side.
For more about Spring's WebSocket support, go to http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/websocket.html.
For more about WebSocket communication, refer to Chapter 8, Replacing HTTP with WebSockets from the book Enterprise Web Development, Yakov Fain, Victor Rasputnis, Anatole Tartakovsky, Viktor Gamov, O'Reilly.