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.
Related
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
I am using spring boot 2.1.6 RELEASE, trying to use Stomp websockets for push notifications. I have taken reference from here : https://github.com/netgloo/spring-boot-samples/tree/master/spring-boot-web-socket-user-notification
Things work fine in my local. When deployed to server with an HTTPS connection, all I see is this in the log.
Handshake failed due to invalid Upgrade header: null
and on the browser
Websocket.js:6 WebSocket connection to 'wss://dev.myserver.in/ws/055/chbvjkl4/websocket' failed
I went through dozens of stackoverflow posts and almost everyone is using proxy server. I am not using any proxy servers. (Please let me know if I should be using one and why)
The code snippets:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
Here is how I have allowed the websocket requests for now
#Override
public void configure(WebSecurity web) throws Exception {
// Tell Spring to ignore securing the handshake endpoint. This allows the
// handshake to take place unauthenticated
web.ignoring().antMatchers("/ws/**");
}
The Push notification service which will be invoked on a particular action:
#Service
public class PushNotificationService {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
/**
* Send notification to users subscribed on channel "/user/queue/notify".
* The message will be sent only to the user with the given username.
*
* #param notification The notification message.
* #param username The username for the user to send notification.
*/
public void sendPushNotification(Notifications notification, String username) {
simpMessagingTemplate.convertAndSendToUser(username, "/queue/notify", notification);
return;
}
}
On the front end:
function connect() {
// Create and init the SockJS object
var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);
// Subscribe the '/notify' channel
stompClient.connect({}, function(frame) {
stompClient.subscribe('/user/queue/notify', function(notification) {
notify(JSON.parse(notification.body));
});
});
And here is the notify
function notify(message) {
let notificationTitle = message.status;
let body = message.createdOn;
let link = message.url;
if(Notification.permission === "granted") {
showPushNotification(notificationTitle,body,link);
}
else {
Notification.requestPermission(permission => {
if(permission === 'granted') {
showPushNotification(notificationTitle,body,link);
}
});
}
}
if you not use like nginx proxy, you should configure spring boot support https.
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
In my application, I need to send real time notifications to a specific user.
My WebSocketConfig class is as below,
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/websocket-example")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
}
}
Most of the time information will be sent by the server side. So I have not set the application destination.
In the client side, I am subscribing to the destination '/topic/user`,
function connect() {
var socket = new SockJS('/websocket-example');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/user', function (greeting) {
// showGreeting(JSON.parse(greeting.body).content);
console.log("Received message through WS");
});
});
}
In one of my RestController I have a method which broadcasts the message to all connected clients.
#GetMapping("/test")
public void test()
{
template.convertAndSend("/topic/user", "Hurray");
}
Until this part everything works fine. I receive the message and is logging to the console.
Now If I want to send the notification only to specific users, I have to use template.convertAndSendToUser(String user, String destination, String message). But I am not understanding what I should pass to the user parameter. Where and when will I get the user?
I went through couple of questions related to this, but I am not understanding the concepts clearly.
Before sending any messages to a user you need to authenticate it by the server first. There are different ways for doing this. Spring Security is a key phrase here
https://docs.spring.io/spring-security/site/docs/current/guides/html5/helloworld-boot.html
When authentication is completed you can simply get user name by calling:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
https://www.baeldung.com/get-user-in-spring-security
This username is part of a java.security.Principal interface. Each StompHeaderAccessor or WebSocket session object has an instance of this principle and you can get the username from it. it is not generated automatically. It has to be generated manually by the server for every session.
You can check here for more info about generating a unique id for every session.
then use like this:
#MessageMapping('/test')
public void test(SimpMessageHeaderAccessor sha)
{
String userName = sha.session.principal.name;
template.convertAndSend(userName, '/topic/user', "Hurray");
}
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()));