I am using Spring Websocket with STOMP, Simple Message Broker.
In my @Controller
I use method-level @SubscribeMapping
, which should subscribe the client to a topic so that the client would receive the messages of that topic afterwards. Let say, the client subscribes to the topic "chat":
stompClient.subscribe('/app/chat', ...);
As the client subscribed to "/app/chat", instead of "/topic/chat", this subscription would go to the method which is mapped using @SubscribeMapping
:
@SubscribeMapping("/chat")
public List getChatInit() {
return Chat.getUsers();
}
Here is what Spring ref. says:
By default the return value from an @SubscribeMapping method is sent as a message directly back to the connected client and does not pass through the broker. This is useful for implementing request-reply message interactions; for example, to fetch application data when the application UI is being initialized.
Okay, this was what I would want, but just partially!! Sending some init-data after subscribing, well. But what about subscribing? It seems to me that the thing what happened here is just a request-reply, like a service. The subscription is just consumed. Please clarify me if this is the case.
- Did the client subscribe to some where, if the broker is not involved in this?
- If later I want to send some message to "chat" subscriptors, would the client receive it? It doesnt seem so.
- Who realizes subscriptions really? Broker? Or some one else?
If here the client is not being subscribed to any where, I wonder why we call this as "subscribe"; because the client receives just one message and not future messages.
EDIT:
To make sure that the subscription has been realized, what I tried is as following:
SERVER-side:
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("/hello").withSockJS();
}
}
Controller:
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
System.out.println("inside greeting");
return new Greeting("Hello, " + message.getName() + "!");
}
@SubscribeMapping("/topic/greetings")
public Greeting try1() {
System.out.println("inside TRY 1");
return new Greeting("Hello, " + "TRY 1" + "!");
}
}
CLIENT-side:
...
stompClient.subscribe('/topic/greetings', function(greeting){
console.log('RECEIVED !!!');
});
stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
...
What I would like to happen:
- When client subscribes to '
/topic/greetings
', the methodtry1
is executed. - When the client sends msg to '
/app/hello
', it should receive the greetings msg which would be@SendTo
'/topic/greetings
'.
Results:
If the client subscribes to
/topic/greetings
, the methodtry1
is UNABLE to catch it.When the client sends msg to '
/app/hello
',greeting
method was executed, and the client received the greetings message. So we understood that it had been subscribed to '/topic/greetings
' correctly.But remember 1. was failed. After some try, it has been possible when the client subscribed to
'/app/topic/greetings'
, i.e. prefixed with/app
(This is understandable by configuration).Now 1. is working, however this time 2. is failed: When the client sends msg to '
/app/hello
', yes,greeting
method was executed, but the client did NOT receive the greetings message. (Because probably now the client was subscribed to the topic prefixed with '/app
', which was unwanted.)
So, what I got is either 1 or 2 of what I would like, but not these 2 together.
- How do I achieve this with this structure (configuring mapping paths correctly) ?
So having both:
does not work as you experienced (as well as me).
The way to solve your situation (as I did mine) is:
Implement an ApplicationListener
If you want to directly reply to a single client use a user destination (see websocket-stomp-user-destination or you could also subscribe to a sub-path e.g. /topic/my-id-42 then you can send a message to this subtopic (I don't know about your exact use case, mine is that I have dedicated subscriptions and I iterate over them if I want to do a broadcast)
Send a message in your onApplicationEvent method of the ApplicationListener as soon as you receive a StompCommand.SUBSCRIBE
Subscription Event Handler:
I faced with the same problem, and finally switched to solution when I subscribe to both
/topic
and/app
on a client, buffering everything received on/topic
handler until/app
-bound one will download all the chat history, that is what@SubscribeMapping
returns. Then I merge all recent chat entries with those received on a/topic
- there could be duplicates in my case.Another working approach was to declare
Obviously, not perfect. But worked :)
(emphasis mine)
Here the Spring Framework documentation is describing what happens with the response message, not the incoming
SUBSCRIBE
message.So to answer your questions:
More on subscription management
With the
SimpleMessageBroker
, the message broker implementation lives in your application instance. Subscription registrations are managed by theDefaultSubscriptionRegistry
. When receiving messages, theSimpleBrokerMessageHandler
handlesSUBSCRIPTION
messages and register subscriptions (see implementation here).With a "real" message broker like RabbitMQ, you've configured a Stomp broker relay that forwards messages to the broker. In that case, the
SUBSCRIBE
messages are forwarded to the broker, in charge of managing subscriptions (see implementation here).Update - more on STOMP message flow
If you take a look at the reference documentation on STOMP message flow, you'll see that:
So here,
/topic/hello
is a broker destination; messages sent there are directly forwarded to the broker. While/app/hello
is an application destination, and is supposed to produce a message to be sent to/topic/hello
, unless@SendTo
says otherwise.Now your updated question is somehow a different one, and without a more precise use case it's difficult to say which pattern is the best to solve this. Here are a few:
/topic/hello
/topic/hello
/app/hello
with a Controller responding with a message right away/app/hello
: use a combination of@MessageMapping
,@SendTo
or a messaging template.If you want a good example, then check out this chat application demonstrating a log of Spring websocket features with a real world use case.