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.
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:
/topic/greetings
', the method try1
is
executed./app/hello
', it should receive the greetings msg which would be @SendTo
'/topic/greetings
'.Results:
If the client subscribes to /topic/greetings
, the method try1
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.
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.