Search code examples
stompspring-websocketstompjs

I can't get @SendToUser to work using STOMP in Spring


I am trying to send message between authenticated users with STOMP in Spring. On the client side I am using STOMP.js.

Controller:

@MessageMapping("/hello")
@SendToUser("/queue/hello")
public HelloMsg hello(HelloMsg message) throws Exception {
    System.out.println("Got message " + message.getMsg());
        return new HelloMsg("Hello, " + HtmlUtils.htmlEscape(message.getMsg()));
}

WebSocketConfig:

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    config.setApplicationDestinationPrefixes("/app");
    config.enableSimpleBroker( "/queue");
    config.setUserDestinationPrefix("/user");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/stomp").setAllowedOrigins("*");
}

For the users I have this in my SecurityConfig:

@Bean
public UserDetailsService userDetailsService() {
    final Properties users = new Properties();
    users.put("user1",   passwordEncoder().encode("password")+",ROLE_USER,enabled");
    users.put("user2", passwordEncoder().encode("password")+",ROLE_USER,enabled");

    return new InMemoryUserDetailsManager(users);
}

From Client Publish:

function handleSubmit(e) {
    e.preventDefault()
    stompClient.publish({
      destination: "/user/"+friend+"/queue/hello",
      body: JSON.stringify({"msg" : "hello there!"})
    })
}

Subscribe:

stompClient.onConnect = (frame) => {
    setConnected(true);
    stompClient.subscribe('/user/queue/hello', (msg) => {
        console.log("Got message: " + msg.body)
    })
} 

I would expect to see the print from the controller on sending message from the client, but there is nothing.

If @SendToUser("/queue/hello") is changed to @SendTo("/app/hello") and the destination is set to "/app/hello", in the client. Message is sent to all users, as expected.

UPDATE

It turns out that message is sent to a specific user. User1 can send message to user2, and it looks good on the client. The problem is that my controller is never called. It looks like the message is sent directly to the user.

I implemented a ChannelInterceptor and printed the message on the inboundChannel:

GenericMessage [payload=byte[22], headers={simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={destination=[/user/user1/queue/hello], content-length=[22]}, simpSessionAttributes={}, simpHeartbeat=[J@3c1aaf6c, simpUser=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user1, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=26CA13D5C5A3DF16C7041FE446000A17], Granted Authorities=[ROLE_USER]], simpSessionId=13716b92-d6fc-7f80-4afc-0bf8b2a7f5ab, simpDestination=/user/user1/queue/hello}]

Also, the only thing that seems to do anything in configureMessageBroker is config.enableSimpleBroker("/queue"). If setApplicationDestinationPrefixes and setUserDestinationPrefix is removed it still "works".

My goal is that a user can send messages to another user, but only when the backend allows communication between the two users.


Solution

  • @SendToUser("/destination") will prefix your /destination with /user/{username} where /user is the userDestinationPrefix. If simpUser header is not found in the message, then internally session-id will be used. Assuming in your case you have user Principal in the message header, the message will be routed to /user/{username}/queue/hello when the handler is invoked. so you will have to subscribe to /user/{username}/queue/hello in your client to receive the message
    Based on your current controller implementation, sending message to /app/hello from client will invoke your message handler.
    From the script which you have posted, you are publishing a message directly to a user queue. Since you didn't share the snippet which you used to subscribe, I am assuming that you are subscribing to "/user/"+friend+"/queue/hello" and you are not receiving any message in that path. If that is the case, please try adding "/user" to the enableSimpleBroker method arguments.