I am using Spring Websocket with STOMP, Simple Message Broker.
In my @Controller
I use method-level @SubscribeMapping
, which should subscribe the clien
Maybe it's not totally related, but when I was subscribing to 'app/test', it was impossible to receive messages sent to 'app/test'.
So I found that adding a broker was the problem (don't know why btw).
So here is my code before :
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic");
}
After :
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
// problem line deleted
}
Now when I subscribe to 'app/test', this is working :
template.convertAndSend("/app/test", stringSample);
In my case, I don't need more.
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
registry.enableSimpleBroker("/app", "/topic");
registry.setApplicationDestinationPrefixes("/app", "/topic");
Obviously, not perfect. But worked :)
Hi Mert though your question is ask over 4 years ago but I'll still try to answer it since I scratched my head on the same problem recently and finally solved it.
The key part here is @SubscribeMapping
is a one-time request-response exchange, therefore the try1()
method in your controller will be triggered only once right after client codes runs
stompClient.subscribe('/topic/greetings', callback)
after that there's no way to trigger try1()
by stompClient.send(...)
Another problem here is the controller is part of the application message handler, which receives destination with prefix /app
ripped, so in order to reach @SubscribeMapping("/topic/greetings")
you actually have to write client code like this
stompClient.subscribe('/app/topic/greetings', callback)
since conventionally topic
is mapped with brokers to avoid ambiguity I recommend to modify your code to
@SubscribeMapping("/greetings")
stompClient.subscribe('/app/greetings', callback)
and now console.log('RECEIVED !!!')
should work.
The official doc also recommends use case scenario of @SubscribeMapping
on initial UI rendering.
When is this useful? Assume that the broker is mapped to /topic and /queue, while application controllers are mapped to /app. In this setup, the broker stores all subscriptions to /topic and /queue that are intended for repeated broadcasts, and there is no need for the application to get involved. A client could also also subscribe to some /app destination, and a controller could return a value in response to that subscription without involving the broker without storing or using the subscription again (effectively a one-time request-reply exchange). One use case for this is populating a UI with initial data on startup.
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.
(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:
With the SimpleMessageBroker
, the message broker implementation lives in your application instance. Subscription registrations are managed by the DefaultSubscriptionRegistry
.
When receiving messages, the SimpleBrokerMessageHandler
handles SUBSCRIPTION
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).
If you take a look at the reference documentation on STOMP message flow, you'll see that:
- Subscriptions to "/topic/greeting" pass through the "clientInboundChannel" and are forwarded to the broker
- Greetings sent to "/app/greeting" pass through the "clientInboundChannel" and are forwarded to the GreetingController. The controller adds the current time, and the return value is passed through the "brokerChannel" as a message to "/topic/greeting" (destination is selected based on a convention but can be overridden via @SendTo).
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.
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:
@Override
public void onApplicationEvent(SessionSubscribeEvent event) {
Message<byte[]> message = event.getMessage();
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getCommand();
if (command.equals(StompCommand.SUBSCRIBE)) {
String sessionId = accessor.getSessionId();
String stompSubscriptionId = accessor.getSubscriptionId();
String destination = accessor.getDestination();
// Handle subscription event here
// e.g. send welcome message to *destination*
}
}