This question already has an answer here:
Spring websocket end-poind and send message
(1 answer)
Closed 7 months ago.
I'm trying to build a Spring STOMP websocket + ActiveMQ service. I have set the websocket and the ActiveMQ queue.
ActiveMQ queue works just fine but I'm not able to make my websocket endpoint send messages to the clients connected to the topic.
Websocket client seems to connect just fine also. The thing is that when the controller receives information it is not caught on the client.
--WebsocketConfig.java--
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
}
--WebsocketController.java--
#Controller
public class WebsocketController {
#Autowired
private ItemService itemService;
#JmsListener(destination = "items-queue")
#MessageMapping("/websocket")
#SendTo("/topic/items")
public String itemsWebsocket(Iterable<Item> items) {
System.out.println("Websocket controller reached");
for (Item item : items) System.out.println(item.getName());
return "hi from websocket";
}
}
--app.js--
let stompClient = null;
function connect() {
let socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/items', function (items) {
appendItems(items);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
function appendItems(items) {
console.log(items);
const itemListContainer = document.getElementById("item-list");
itemListContainer.innerText = "";
Array.from(items).forEach( item => {
const itemContainer = document.createElement("div");
itemContainer.innerText = item.name;
itemListContainer.append(itemContainer);
});
}
connect();
I just found out that if I apply the changes from here it does the thing.
Related
I am trying to run a websocket example having java code in one port(8080) and java script code in another port(8080)
problem : when i deploy client and server on same port it works properly but when client app.js is on different port than server port it throws error
Access to XMLHttpRequest at
'http://localhost:8091/websocket-example/info?t=1601539296700' from
origin 'http://localhost:8080' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
java configuration file
#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");
registry.setApplicationDestinationPrefixes("/app");
}
}
contoller file
#Controller
public class ChattingController {
#MessageMapping("/user")
#SendTo("/topic/user")
public UserResponse getUser(User user) {
return new UserResponse("Hi " + user.getName());
}
}
app.js
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#userinfo").html("");
}
function connect() {
var socket = new SockJS('http://localhost:8091/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);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/user", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
$("#userinfo").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});
springsecurity file
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer, WebMvcRegistrations {
private static final String EVERYTHING = "/**";
#Bean
public SuperInterceptor superInterceptor() {
return new SuperInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> paths = new ArrayList<String>();
paths.add("/login");
paths.add("/user/register");
registry.addInterceptor(superInterceptor()).addPathPatterns(EVERYTHING).excludePathPatterns(paths);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable();
httpSecurity.cors();
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
}
ps: i am new to websocket sorry if this is a naive issue
If you configure it with Spring Security then try this, it might helps you. Add it in the WebSecurityConfig file of Spring Security.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
}
OR
If you are just configure the Spring MVC then try this:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST","PUT", "DELETE");
}
};
}
I have an application with a large number of groups, where my server is using a message queue (RabbitMQ) to observe the groups and post notification to the user upon changes over WebSocket. I'm using Spring boot and their WebSocket implementation inspired by this guide: https://spring.io/guides/gs/messaging-stomp-websocket/
Here is an example of the JavaScript client subscribing to the channel:
var socket = new SockJS('http://localhost/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/group/1/notification', function (message) {
// to something..
});
});
My Java Spring WebSocket controller has this broadcastNotification method sending messages to the /topic/group/{groupId}/notification channel.
#Controller
public class GroupController {
private SimpMessagingTemplate template;
#Autowired
public GroupController(SimpMessagingTemplate template) {
this.template = template;
}
public void broadcastNotification(int groupId, Notification notification) {
this.template.convertAndSend("/topic/group/." + tenantId + "/notification", Notification);
}
}
Thats working fine, but with performacne in mind I would like my to business logic to only observe groups currently beeing subscribed on WebSocket.
How can I be notified on my server when clients subscribe to the /topic/group/1/notification or /topic/group/1/* channel? The web users will be subscribing and unsubscribing as they browse the web page.
You can listen to the event SessionSubscribeEvent like this:
#Component
public class WebSocketEventListener {
#EventListener
public void handleSessionSubscribeEvent(SessionSubscribeEvent event) {
GenericMessage message = (GenericMessage) event.getMessage();
String simpDestination = (String) message.getHeaders().get("simpDestination");
if (simpDestination.startsWith("/topic/group/1")) {
// do stuff
}
}
}
You can use annotation-driven event listener (Kotlin code):
#EventListener
private fun onSubscribeEvent(event: SessionSubscribeEvent) {
// do stuff...
}
Such event listeners can be registered on any public method of a managed bean via the #EventListener annotation.
Spring websockets events docs
Spring events examples
You can detect when a client subscribes to a topic using interceptors in the WebSocketConfig class:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration){
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if(StompCommand.CONNECT.equals(accessor.getCommand())){
System.out.println("Connect ");
} else if(StompCommand.SUBSCRIBE.equals(accessor.getCommand())){
System.out.println("Subscribe ");
} else if(StompCommand.SEND.equals(accessor.getCommand())){
System.out.println("Send message " );
} else if(StompCommand.DISCONNECT.equals(accessor.getCommand())){
System.out.println("Exit ");
} else {
}
return message;
}
});
}
}
The accessor object contains all information sent from the client.
I'm implementing a simple socket like this :
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
From the client-side :
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
And the websocket configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
Now I would like to apply authentication for the socket so that not all clients can connect to server. I may look like
var socket = new SockJS('/gs-guide-websocket?token= a JWT token'); //or sth similar
from the client-side.
Let just assume that I can hide the token from people who inspect the frontend code, how do I get that token and verify it from the server side ? (Provided that I have the function to verify the JWT already)
Or is there any better way of implementing security for socket connection that you can suggest ?
I have followed Quetion1 and Quetion2 from stack overflow to send messages to specific client, based on its sessionId but could not find success.
Below is my sample RestController class
#RestController
public class SpringSessionTestApi {
#Autowired
public SimpMessageSendingOperations messagingTemplate;
#MessageMapping("/messages")
public void greeting(HelloMessage message, SimpMessageHeaderAccessor headerAccessor) throws Exception {
String sessionId = (String) headerAccessor.getSessionAttributes().get("SPRING.SESSION.ID");
messagingTemplate.convertAndSendToUser(sessionId,"/queue/test",message, createHeaders(sessionId));
}
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
}
Session Id: when client sends createSession request, new spring sessionId is generated and same is stored in MongoDB as well. After that when client sends web socket connect request, same sessionId is received which was stored in mongoDb as expected. Till This everything is working fine.
Now my job is to send response back to the client based on the sessionId.
For that I have below web socket class:
#Configuration
#EnableScheduling
#EnableWebSocketMessageBroker
public class WebSocketConfig extends
AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
#Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages");
}
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}
and the sample client code that I am using to connect is:
function connect() {
stompClient = Stomp.client('ws://localhost:8016/messages');
stompClient.debug = null;
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/queue/test', function (greeting) {
console.log("Hello "+greeting);
console.log("Greeting body "+JSON.parse(greeting.body));
});
});
}
Please help, Where I am doing wrong in this?
Thanks in Advance!
If you are using /user channel as you do, try to pass the user as stated here.
#MessageMapping("/messages")
public void greeting(HelloMessage message, SimpMessageHeaderAccessor headerAccessor, Principal principal)
throws Exception {
messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/test", message);
}
I've found a full workable Spring Stomp Chat project in git, the link is here. You can refer to it.
https://gist.github.com/theotherian/9906304
I have added custom token based authentication for my spring-web app and extending the same for spring websocket as shown below
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
if (!StringUtils.isEmpty(jwtToken)) {
Authentication auth = tokenService.retrieveUserAuthToken(jwtToken);
SecurityContextHolder.getContext().setAuthentication(auth);
accessor.setUser(auth);
//for Auth-Token '12345token' the user name is 'user1' as auth.getName() returns 'user1'
}
}
return message;
}
});
}
}
The client side code to connect to the socket is
var socket = new SockJS('http://localhost:8080/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({'Auth-Token': '12345token'}, function (frame) {
stompClient.subscribe('/user/queue/greetings', function (greeting) {
alert(greeting.body);
});
});
And from my controller I am sending message as
messagingTemplate.convertAndSendToUser("user1", "/queue/greetings", "Hi User1");
For the auth token 12345token the user name is user1. But when I send a message to user1, its not received at the client end. Is there anything I am missing with this?
In your Websocket controller you should do something like this :
#Controller
public class GreetingController {
#Autowired
private SimpMessagingTemplate messagingTemplate;
#MessageMapping("/hello")
public void greeting(Principal principal, HelloMessage message) throws Exception {
Greeting greeting = new Greeting();
greeting.setContent("Hello!");
messagingTemplate.convertAndSendToUser(message.getToUser(), "/queue/reply", greeting);
}
}
On the client side, your user should subscribe to topic /user/queue/reply.
You must also add some destination prefixes :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue" ,"/user");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
/*...*/
}
When your server receive a message on the /app/hello queue, it should send a message to the user in your dto. User must be equal to the user's principal.
I think the only problem in your code is that your "/user" is not in your destination prefixes. Your greetings messages are blocked because you sent them in a queue that begin with /user and this prefixe is not registered.
You can check the sources at git repo :
https://github.com/simvetanylen/test-spring-websocket
Hope it works!
In my previous project I sent messages to one specific user; in detail I wrote the following:
CLIENT SIDE:
function stompConnect(notificationTmpl)
{
var socket = new SockJS('/comm-svr');
stompClient = Stomp.over(socket);
var theUserId
stompClient.connect({userId:theUserId}, function (frame) {
debug('Connected: ' + frame);
stompClient.subscribe('/topic/connect/'+theUserId, function (data) {
//Handle data
}
});
}
SERVER SIDE
Spring websocket listener:
#Component
public class WebSocketSessionListener
{
private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionListener.class.getName());
private List<String> connectedClientId = new ArrayList<String>();
#EventListener
public void connectionEstablished(SessionConnectedEvent sce)
{
MessageHeaders msgHeaders = sce.getMessage().getHeaders();
Principal princ = (Principal) msgHeaders.get("simpUser");
StompHeaderAccessor sha = StompHeaderAccessor.wrap(sce.getMessage());
List<String> nativeHeaders = sha.getNativeHeader("userId");
if( nativeHeaders != null )
{
String userId = nativeHeaders.get(0);
connectedClientId.add(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
else
{
String userId = princ.getName();
connectedClientId.add(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
}
#EventListener
public void webSockectDisconnect(SessionDisconnectEvent sde)
{
MessageHeaders msgHeaders = sde.getMessage().getHeaders();
Principal princ = (Principal) msgHeaders.get("simpUser");
StompHeaderAccessor sha = StompHeaderAccessor.wrap(sde.getMessage());
List<String> nativeHeaders = sha.getNativeHeader("userId");
if( nativeHeaders != null )
{
String userId = nativeHeaders.get(0);
connectedClientId.remove(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
else
{
String userId = princ.getName();
connectedClientId.remove(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
}
public List<String> getConnectedClientId()
{
return connectedClientId;
}
public void setConnectedClientId(List<String> connectedClientId)
{
this.connectedClientId = connectedClientId;
}
}
Spring websocket message sender:
#Autowired
private SimpMessagingTemplate msgTmp;
private void propagateDvcMsg( WebDeviceStatusInfo device )
{
String msg = "";
String userId =((Principal)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getName()
msgTmp.convertAndSend("/topic/connect"+userId, msg);
}
I hope it's useful