How to send message to websocket connected user by a Servcie - spring-boot

I have a client side app ( ReatJS ) which instantiates a webSocket connection with the server ( Spring Boot ) and waits to receive data.
At a certain moment the server has to send data to a specific user on the WebSocket channel, the call to the function that starts the sending is inside a class annotated with #Service
How do I send a message using WebFlux via WebSocket to a specific user? I identify the user by id or by his connection-id
These are some classes
#Configuration
public class WebSocketConfig {
#Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>(){{
put("/data", new MyHandler());
}};
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return mapping;
}
}
public class MyHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession webSocketSession) {
return webSocketSession.send( ... );
}
}
#Service
public class MyService {
public void sendToUser(String userId, String connectionId, String message) {
// send message to connected user through WebSocket
}
}

Related

Is there a way to send websocket message from regular method in spring

I am building web application. There are admin and user roles provided. When user making some action admin is recieving a message that something happened. Websocket connection establishing when user logged. Is there a way to not create ws connection for user and use only HHTP protocol to sending message and send WS message from controller method only?
Now i have theese settings:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
}
}
#Controller
public class NotificationController {
#MessageMapping("/notification")
#SendTo("/topic/test")
public Notification getNotification(Notification notification) {
return notification;
}
}
Yes it is possible.
You have to inject SimpleMessagintTemplate, with #Autowire or with constructor.
private final SimpMessagingTemplate simpMessagingTemplate;
public ConstructorName(SimpMessagingTemplate simpMessagingTemplate){
this.simpMessagingTemplate = simpMessagingTemplate;
}
In your controller, or function where you want to send the message to the client use the convertAndSendToUser function.
simpMessagingTemplate.convertAndSendToUser("userId","/private", messageData);
On javascript client side.
var Sock = new SockJS('http://localhost:8080/ws');
stompClient = over(Sock);
stompClient.connect({}, onConnected, onError);
stompClient.subscribe('/topic/' + userId + '/private', onMessageReceived);

How to pass message to controller by #MessageMapping with specified user url?

I have such problem. When i try to send message from client side to server, it doesn't match with my #MessageMapping methods. I don't know how to intercept messages on controller layer.
Client side
sends message (it's react-stomp that uses sockjs):
move = (move) => {
this.clientRef.sendMessage("/user/${this.state.opponentId}/queue/move", JSON.stringify(move))
};
Server side. WebSocketConfig:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/handler")
.setHandshakeHandler(new CustomHandshakeHandler())
.setAllowedOrigins("http://localhost:3000")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry brokerRegistry) {
brokerRegistry.setApplicationDestinationPrefixes("/app");
brokerRegistry.enableSimpleBroker("/topic", "/queue", "/user");
}
#EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new MyChannelInterceptor());
}
}
I also added interceptor class to check path of incomming message:
public class MyChannelInterceptor implements ChannelInterceptor {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
return message;
}
}
On debugging of MyChannelInterceptor i see message with payload and headers. There is simpDestination header with such value:
/user/baedde36-0f9e-4fa5-b8d7-687db1dbcd67/queue/move
What #MessageMapping value should i write to handle messages from specified users? This message succesfully gets to frontside by subscription on this topic but doesn't stay on any controller:
`/user/${message}/queue/move`
I just want to handle messages on server side but i can't catch it there.
Okay. As i understood there is 3 ways to handle websocket messages:
/app - handles with controller
/user - handles with broker, sends messages to specific users
/topic - broadcast to topic for all subscribers
In my situation i just need to create json object with userId, receiverId and message. On server side add DTO class and get it as attribute in my controller method.
Solution:
move = (move) => {
let moveDto = {move: move, userId: this.state.userId, opponentId: this.state.opponentId}
this.clientRef.sendMessage(`/app/move`, JSON.stringify(moveDto))
};
Server side:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MoveDto {
private String userId;
private String opponentId;
private int move;
}
Controller class:
#RestController
public class GameController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
...//some code here
#MessageMapping("/move")
public void message(MoveDto moveDto) {
String userMessage= "foo";
String opponentMessage = "bar";
simpMessagingTemplate.convertAndSendToUser(
moveDto.getUserId(), "/queue/message", userMessage);
simpMessagingTemplate.convertAndSendToUser(
moveDto.getOpponentId(), "/queue/message", opponentMessage );
}

spring websocket send messages to specific users

I am developing a Springboot application which has a websocket server to handle messages coming from different users. I need to develop this server such that a socket client can send messages to specific client. Below given is my WebSocketHandler class.
public class WebSocketHandler extends AbstractWebSocketHandler {
static Logger logger = LogManager.getLogger(WebSocketHandler.class);
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payLoad = message.getPayLoad();
session.sendMessage("message : "+payLoad+" is received!!!");
////
}
}
}
}
I prefer to use a one handler instance as given below.
public class WebSocketConfiguration implements WebSocketConfigurer {
static WebSocketHandler handler = new WebSocketHandler();
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(handler,"/websocket");
}
}
Is there way to achieve this ? Thanks everyone.

Spring get open websocket connections

I am using Spring Boot Websocket to enable my Spring Boot 2 microservice to deal with websocket connections.
My config:
#Configuration
#EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
#Autowired
WebSocketHandler webSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler();
handshakeHandler.setSupportedProtocols(HANDSHAKE_PROTOCOL);
registry.addHandler(webSocketHandler, WS_HANDLER_PATH + WILDCARD)
.setAllowedOrigins("*")
.setHandshakeHandler(handshakeHandler);
}
}
My clients are able to connect to my Service via Websocket. I am implementing WebSocketHandler interface to handle messages and log connections.
Now my question: Is there a way to show all current websocket users/sessions?
I was trying to use the SimpUserRegistry:
#Configuration
public class UserConfig {
final private SimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
#Bean
public SimpUserRegistry userRegistry() {
return userRegistry;
}
}
and to show the users via a REST endpoint
#RestController
public class WebSocketManager {
private final SimpUserRegistry userRegistry;
public WebSocketManager(SimpUserRegistry userRegistry) {
this.userRegistry = userRegistry;
}
#GetMapping(path = "/users")
public List<String> getConnectedUsers() {
userRegistry.getUsers().stream()
.map(SimpUser::getName)
.forEach(System.out::println);
System.out.println("Users " + userRegistry.getUsers());
System.out.println("UsersCount " + userRegistry.getUserCount());
return this.userRegistry
.getUsers()
.stream()
.map(SimpUser::getName)
.collect(Collectors.toList());
}
}
But this always gives my an empty list: [] even when obviously WS connections are established.
Is this SimpUserRegistry working with the Websocket system of Spring which is configured with the WebSocketConfigurer and #EnableWebSocket? What am I doing wrong? Any tips or alternatives?
Thank you in advance!

Migrating existing Spring Websocket handler to use rsocket

Suppose I have this simple Websocket handler for chat messages:
#Override
public Mono<Void> handle(WebSocketSession webSocketSession) {
webSocketSession
.receive()
.map(webSocketMessage -> webSocketMessage.getPayloadAsText())
.map(textMessage -> textMessageToGreeting(textMessage))
.doOnNext(greeting-> greetingPublisher.push(greeting))
.subscribe();
final Flux<WebSocketMessage> message = publisher
.map(greet -> processGreeting(webSocketSession, greet));
return webSocketSession.send(message);
}
What is needed to be done here in general as such it will use the rsocket protocol?
RSocket controller in the Spring WebFlux looks more like a RestController than WebSocketHandler. So the example above is simple like that:
#Controller
public class RSocketController {
#MessageMapping("say.hello")
public Mono<String> saHello(String name) {
return Mono.just("server says hello " + name);
}
}
and this is equivalent to requestResponse method.
If this answer doesn't satisfy you, please describe more what you want to achieve.
EDIT
If you want to broadcast messages to all clients, they need to subscribe to the same Flux.
public class GreetingPublisher {
final FluxProcessor processor;
final FluxSink sink;
public GreetingPublisher() {
this.processor = DirectProcessor.<String>create().serialize();
this.sink = processor.sink();
}
public void addGreetings(String greeting) {
this.sink.next(greeting);
}
public Flux<String> greetings() {
return processor;
}
}
#Controller
public class GreetingController{
final GreetingPublisher greetingPublisher = new GreetingPublisher();
#MessageMapping("greetings.add")
public void addGreetings(String name) {
greetingPublisher.addGreetings("Hello, " + name);
}
#MessageMapping("greetings")
public Flux<String> sayHello() {
return greetingPublisher.greetings();
}
}
Your clients have to call the greetings endpoint with the requestStream method. Wherever you send the message with the greetingPublisher.addGreetings() it's going to be broadcasted to all clients.

Resources