spring websocket send messages to specific users - spring-boot

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.

Related

How to send message to websocket connected user by a Servcie

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
}
}

Capturing a message from unity using WebSocketSharp on a Spring server

I can't solve the problem. It is necessary to send messages to the server through the socket and process them. I can intercept the subscription, unsubscribe, connection and disconnection events on the server. But I can’t understand how to send a message to a specific destonation and intercept it on the server. On the client I use the library https://github.com/sta/websocket-sharp
Server code
#Configuration
#EnableWebSocket
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer implements WebSocketConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/multiplayer");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/multiplayer").setAllowedOrigins("*").withSockJS();
}
#Autowired
MessageHandler messageHandler;
// message interception option
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(messageHandler, "/result-game");
}
}
In the registerWebSocketHandlers method, I register a listener, it intercepts messages only if you specify "/multiplayer/websocket" in the "/result-game" place, the only trouble is that it intercepts everything in general and the #EventListaner listeners that intercepted my connection events stop working , shutdowns, etc. And when you try to send data to the client on the socket, the client does not receive them.
Client code
var DOMAIN = "1.1.0.1:8080";
_webSocket = new WebSocket($"ws://{DOMAIN}/multiplayer/websocket");
_webSocket.OnOpen += (sender, e) =>
{
// Sending a connection message
StompMessageSerializer serializer = new StompMessageSerializer();
var connect = new StompMessage("CONNECT");
connect["accept-version"] = "1.1";
connect["heart-beat"] = "10000,10000";
connect["playerId"] = _id;
var s = serializer.Serialize(connect);
SubscribeSocket(clientId.ToString(), $"/multiplayer/connect/{_id}", ConnectServer);
_webSocket.Send(s);
// Attempt to send messages to a specific destination
var content = new { Subject = "Stomp client", Message = "Hello World!!" };
var broad = new StompMessage("SEND", JsonConvert.SerializeObject(content));
broad["content-type"] = "application/json";
broad["destination"] = "/result-game";
_webSocket.Send(serializer.Serialize(broad));
Debug.LogError(TAG + "Connect open");
};
_webSocket.ConnectAsync();
On the server, in addition to the option above, there are two more attempts to intercept
First
#Slf4j
#Controller
#RequiredArgsConstructor
public class MessageController{
#MessageMapping("/result-game")
public void say(String message) throws InterruptedException {
Thread.sleep(30);
}
}
Second
#ServerEndpoint("/result-game")
public class MessageHandle {
#OnMessage
public void handleMessage(Session session, String message) {
// Do something with the message
System.out.println("Received message: " + message);
}
#OnMessage
public void processGreeting(String message, Session session) {
System.out.println("Greeting received:" + message);
}
}
Could someone tell me what I'm doing wrong with spring and working with sockets for the first time

How to send message using websocket whenever an API is called using springboot?

I have a simple controller which return name . I have websocket handler which return message to client as: Hey there, presentation recieved from user. whenever http://localhost:8080/sample is called, i need to display the above message to <ws://localhost:8080/presentation>, using https://websocketking.com/ to connect to websocket.
#RestController
public class WebController {
#RequestMapping("/sample")
public SampleResponse Sample(#RequestParam(value = "name",
defaultValue = "Robot") String name) {
SampleResponse response = new SampleResponse();
response.setId(1);
response.setMessage("Your name is "+name);
return response;
}
}
#Component
public class WebSocketHandler extends AbstractWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("New Text Message Received from presetation");
String payload = message.getPayload();
System.out.println(payload);
session.sendMessage(new TextMessage("Hey there, presentation recieved from user"));
}
}
public class WebSocketConfiguration implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketHandler(), "/presentation").setAllowedOrigins("*");
}
}

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 - How can I detect client disconnect

I am new to spring
I have this class :
public class Server extends TextWebSocketHandler implements WebSocketHandler {
WebSocketSession clientsession;
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
clientsession = session;
}
I need to detect a client disconnect on clientsession.
implement ApplicationListener but its not clear how I can register the listener?
do I need to do it in my web.xml ?
The WebSocketHandler afterConnectionClosed function is called after a websocket client disconnects. You simply need to override this in the manner that you override handleTextMessage.
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus){
// your code here
}
There may be substantial delay between the client disconnect and your server event detection. See details about real-time disconnection detection.
You will need to override configureClientOutboundChannel and configureClientInboundChannel of AbstractWebSocketMessageBrokerConfigurer, providing your interceptor
Another way is using ApplicationEvents.
Both methods are described here:
http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/
public class StompConnectEvent implements ApplicationListener<SessionConnectEvent> {
private final Log logger = LogFactory.getLog(StompConnectEvent.class);
public void onApplicationEvent(SessionConnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
String company = sha.getNativeHeader("company").get(0);
logger.debug("Connect event [sessionId: " + sha.getSessionId() +"; company: "+ company + " ]");
}
}
I hope that help. Let me know if I need to explain more.
You can use listeners to detect when session is connected or closed.
More information about listeners you can find by this link.
Example how to detect connected session:
#Component
public class SessionConnectedEventListener implements ApplicationListener<SessionConnectedEvent> {
private IWebSocketSessionService webSocketSessionService;
public SessionConnectedEventListener(IWebSocketSessionService webSocketSessionService) {
this.webSocketSessionService = webSocketSessionService;
}
#Override
public void onApplicationEvent(SessionConnectedEvent event) {
webSocketSessionService.saveSession(event);
}
}
Example how to detect when session is disconneted:
#Component
public class SessionDisconnectEventListener implements ApplicationListener<SessionDisconnectEvent> {
private IWebSocketSessionService webSocketSessionService;
public SessionDisconnectEventListener(IWebSocketSessionService webSocketSessionService) {
this.webSocketSessionService = webSocketSessionService;
}
#Override
public void onApplicationEvent(SessionDisconnectEvent event) {
webSocketSessionService.removeSession(event);
}
}
What you probably want to achieve is maintaining multiple sessions. Something like this:
public class Server extends TextWebSocketHandler implements WebSocketHandler {
private List<WebSocketSession> sessions = new ArrayList<>();
#Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// clientsession = session;
// Send individual or broadcast messages here ...
session.sendMessage(new TextMessage(textMessage + "!"));
}
}

Resources