Spring: send message to websocket clients - java

I'm building a webchat with Spring Boot, RabbitMQ and WebSocket as POC, but I'm stucked a the last point: WebSockets
I want my ws clients to connect to a specific endpoint, like /room/{id} and when a new message arrives, I want the server to send the response to clients, but I searched for something similar and didn't found.
Currently, when the message arrives, I process it with RabbitMQ, like
container.setMessageListener(new MessageListenerAdapter(){
#Override
public void onMessage(org.springframework.amqp.core.Message message, Channel channel) throws Exception {
log.info(message);
log.info("Got: "+ new String(message.getBody()));
}
});
what I would like is, instead log it , I want to send it to the client, for example: websocketManager.sendMessage(new String(message.getBody()))

Ok, I think I got it, for everyone who needs it, here is the answer:
first, you need to add WS dependencies to the pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
create a WS endpoint
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// the endpoint for websocket connections
registry.addEndpoint("/stomp").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/");
// use the /app prefix for others
config.setApplicationDestinationPrefixes("/app");
}
}
Note: I'm using STOMP, so the clients should connect like this
<script type="text/javascript">
$(document).ready(function() {
var messageList = $("#messages");
// defined a connection to a new socket endpoint
var socket = new SockJS('/stomp');
var stompClient = Stomp.over(socket);
stompClient.connect({ }, function(frame) {
// subscribe to the /topic/message endpoint
stompClient.subscribe("/room.2", function(data) {
var message = data.body;
messageList.append("<li>" + message + "</li>");
});
});
});
</script>
Then, you can simply wire the ws messenger on your components with
#Autowired
private SimpMessagingTemplate webSocket;
and send the message with
webSocket.convertAndSend(channel, new String(message.getBody()));

Related

WebSocket client can not connect to Java spring WebSocket server using different port

I am using Java Spring WebSocket to create a WebSocket server, it actually works if WebSocket client is created from the SAME server.
This is my server side code, very simple
// WebSocketConfig.java
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Autowired
SocketTextHandler socketTextHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(socketTextHandler, "/sockets");
}
}
// SocketTextHandler.java
#Component
public class SocketTextHandler extends TextWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
throws InterruptedException, IOException {
String payload = message.getPayload();
System.out.println("16: " + session.getId());
session.sendMessage(new TextMessage("Resp: " + payload));
}
#Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
System.out.println("Server transport error: " + exception.getMessage());
}
}
Then I put the following WebSocket client in the Spring boot static index.html
<html>
<body>
Hello
<script>
console.log('start');
const ws = new WebSocket('ws://localhost:8080/sockets');
ws.onopen = function(evt) {
console.log('onopen', evt);
}
ws.onerror = function(evt) {
console.log('onerror', evt);
}
console.log('done');
</script>
</body>
</html>
Then I visit http://localhost:8080/index.html, there is no error and I can see the console.log('onopen', evt) log.
However, my WebSocket client should be running on a different port http://localhost:3000, I use the same index.html, but when I visit http://localhost:3000/index.html, there is always this error WebSocket connection to 'ws://localhost:8080/sockets' failed in console
I am wondering are WebSockets required to be connected from the same server? Or I missed something.
Thanks!
for anyone who also encounter this, registry.addHandler(socketTextHandler, "/sockets").setAllowedOrigins("*"); will resolve the issue

STOMP destination url vs endpoint url

The following code is from spring mvc documentation:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
#Controller
public class GreetingController {
#MessageMapping("/greeting") {
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
The client connects to http://localhost:8080/portfolio to establish WebSocket connection, I wonder what's the exact url of client sending request?
http://localhost:8080/portfolio/app
or
http://localhost:8080/app?
and in actual WebSocket frame, does the destination header contain relative url like /app, /topic or absolute url?
[Android] https://github.com/NaikSoftware/StompProtocolAndroid
[Spring] https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/web.html#websocket-stomp
Just set the end point by using
addEndpoint("/portfolio");
Use the following Url to connect to websocket
ws://localhost:8080/portfolio
But remember you have to connect to socket only once and after that just invoke the endpoints without URL. Beacause socket is streamline connection and you have to establish connection only once.
setApplicationDestinationPrefixes("/app");
Above line will set the end point /app using this you can only publish over the socket. However all who has subscribed to this topic will get notified.
enableSimpleBroker("/topic");
Broker are responsible for handling subscribe and publish for both as they listen and send data in dual way means publish and subscribe both unlike /app.
private var mStompClient: StompClient? = null
mStompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, "ws://localhost:8080/portfolio")
Connect to websocket using the above line. since we have to connect to socket end point only once write this in singleton.
val response = stomp.topic("/topic")
.subscribe { topicMessage -> }
Now above line will subscribe to your socket client means anytime you pushed the data from /topic this will this response variable will notified.
stompClient.send(StompMessage(StompCommand.SEND,
listOf(StompHeader(StompHeader.DESTINATION, "/topic")),
gson.toJson(myDataModel)))?
.subscribe()
Using above line you will you will you will send data to the socket which is specified as /topic.
#MessageMapping("/action")
fun performDeviceAction(#Payload myDataModel: MyDataModel) {}
Use the above line to receive the data from client on socket /action
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/user");
}
In order to tell Spring to forward client requests to the endpoint , we need to register the handler. Above snipplet will register a client.
Use below link and download source code for more information
https://www.javainuse.com/spring/boot-websocket

What SockJs client url to use when serving UI on webpack-dev-server?

I have the following code on the client side:
var sock = new SockJS('http://localhost:8443/quote-socket');
const stompClient = Stomp.over(sock);//connect using your client
stompClient.connect({}, onConnected, onError);
function onConnected() {
stompClient.subscribe('/topic/prices', payload => {
console.log('DATA COMING!');
console.log(payload);
});
stompClient.send("ws/quote/FB",
{},
JSON.stringify({})
);
}
function onError(error) {
console.log('there is an error');
console.log(error);
}
And this is the Spring boot (i.e. server) side:
#Override
#MessageMapping("/quote/{symbol}")
#SendTo("/topic/prices")
public String getSingleQuote(#DestinationVariable("symbol") String symbol, HttpServletResponse response) {
return "Hello";
}
And this is the web socket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/quote-socket").setAllowedOrigins("http://localhost:8080").withSockJS();
}
}
I can see the following logs in the browser's console:
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.0,1.1,1.2
heart-beat:10000,10000
Received data
<<< CONNECTED
heart-beat:0,0
version:1.2
content-length:0
connected to server undefined
>>> SUBSCRIBE
id:sub-0
destination:/topic/prices
>>> SEND
destination:ws/quote/FB
content-length:2
But the controller method doesn't get called, and I don't see the payload on the browser side. What is the problem here?
Update 1
I found out that it have to use SockJs like this:
var sock = new SockJS('api/quote-socket');
But during development I run the UI dev server (webpack-dev-server) under a different port, which means I need to use ws protocol, and as SockJS doesn't support it I have to use StopJS.client:
var url = "ws://localhost:8443/api/quote-socket";
var stompClient = Stomp.client(url);
Is there a way to use ws on SockJs? How should we with different UI server port during development, which seems to be pretty common.

WebSocket Handshake - Unexpected response code 200 - AngularJs and Spring Boot

When I tried to establish websocket communication between AngularJS app and Spring Boot I'm getting the error: Error during websocket handshake - Unexpected response code: 200.
Here is my JS code:
function run(stateHandler, translationHandler, $websocket) {
stateHandler.initialize();
translationHandler.initialize();
var ws = $websocket.$new('ws://localhost:8080/socket'); // instance of ngWebsocket, handled by $websocket service
ws.$on('$open', function () {
console.log('Oh my gosh, websocket is really open! Fukken awesome!');
});
ws.$on('/topic/notification', function (data) {
console.log('The websocket server has sent the following data:');
console.log(data);
ws.$close();
});
ws.$on('$close', function () {
console.log('Noooooooooou, I want to have more fun with ngWebsocket, damn it!');
});
}
And here is my Java code:
WebsocketConfiguration.java
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry)
{
stompEndpointRegistry.addEndpoint("/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
}
WebsocketSecurityConfiguration.java
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
// message types other than MESSAGE and SUBSCRIBE
.nullDestMatcher().authenticated()
// matches any destination that starts with /rooms/
.simpDestMatchers("/topic/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.simpDestMatchers("/topic/**").permitAll()
// (i.e. cannot send messages directly to /topic/, /queue/)
// (i.e. cannot subscribe to /topic/messages/* to get messages sent to
// /topic/messages-user<id>)
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()
// catch all
.anyMessage().denyAll();
}
Does anyone have an idea how to fix this problem?
Thank you in advance!
I had a similiar problem, I was testing my websocket connection using an chrome plugin (Simple WebSocket Client) and was trying to connect to ws://localhost:8080/handler/ which is defined in my code as registry.addEndpoint("/handler").setAllowedOrigins("*").withSockJS(); but unexpected error 200 was occuring. I've fixed this by appending /websocket to my client request string on the chrome extension, so what you could try is to change in your JS file the following line:
var ws = $websocket.$new('ws://localhost:8080/socket');
to
var ws = $websocket.$new('ws://localhost:8080/socket/websocket');
I dont know the reason why this fixed it in my case i just randomly stumbled upon it, if some1 could clarify it more it would be really nice :)
Can you try this WebsocketConfiguration configuration:
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry)
{
stompEndpointRegistry.addEndpoint("/socket").setAllowedOrigins("*");
stompEndpointRegistry.addEndpoint("/socket").setAllowedOrigins("*").withSockJS();
}
so you have both websocket and SockJS configurations?
You need to use a sockJS client if you configure it to use sockJS on your websocket endpoint.

private messages spring websocket

I am learning spring websocket and I am stuck on how I can send messages to specific users using #DestinationVariable("username")
Here is my code
configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketContextConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
#Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-cfg").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/","/topic","/exchange/")
.setRelayHost("localhost");
registry.setApplicationDestinationPrefixes("/app");
}
}
Controller
#MessageMapping("/chat.private.{username}")
public void filterPrivateMessage(#Payload Message message, #DestinationVariable("username") String username, Principal principal) {
this.simpMessagingTemplate.convertAndSendToUser(username, "/queue/chat.message", message);
}
the client code
var stomp = null;
stomp = Stomp.over(new SockJS("/ws-cfg"));
stomp.connect('guest', 'guest', function(frame) {
stomp.subscribe("/user/queue/chat.message", function (frame) {
dysplayMessage(JSON.parse(frame.body));
});
})
$("#sendMessage").click(function (event) {
event.preventDefault();
var message = $('#text').val();
var username="user#gmail.com";// i am lost in this part, i supose this must be the #DestinationVariable("username")
destination = "/app/chat.private."+to;
stomp.send(destination, {}, JSON.stringify({'text': message}));
$('#text').val("");
});
I am currently using websocket with spring security. How can I set the #DestinationVariable("username") on stomp.send method.
Thanks in advance.
Check out this Spring WebSocket Chat sample which has what you are looking for: https://github.com/salmar/spring-websocket-chat
The Destination Variable gets populated from the payload you are sending. In your case, you have to include it like this
stomp.send(destination, {}, JSON.stringify({'text': message, 'username' : 'User1'}));
One more observation is you don't seem to be setting the UserDestinationPrefix. you need to set it up for both the MessageBrokerRegistry and SimpMessagingTemplate.

Categories