I would like to use Spring Messaging to create a real time notification system for logged users for my webapp.
I defined a AbstractWebSocketMessageBrokerConfigurer as follows:
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notifications").withSockJS()
.setSessionCookieNeeded(true)
.setWebSocketEnabled(true);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic/", "/queue/");
}
And, according to the documentation:
An application can send messages targeting a specific user. Spring’s STOMP support recognizes destinations prefixed with "/user/". For example, a client might subscribe to the destination "/user/queue/position-updates". This destination will be handled by the UserDestinationMessageHandler and transformed into a destination unique to the user session, e.g. "/queue/position-updates-user123". This provides the convenience of subscribing to a generically named destination while at the same time ensuring no collisions with other users subscribing to the same destination so that each user can receive unique stock position updates.
On the sending side messages can be sent to a destination such as "/user/{username}/queue/position-updates", which in turn will be translated by the UserDestinationMessageHandler into one or more destinations, one for each session associated with the user. This allows any component within the application to send messages targeting a specific user without necessarily knowing anything more than their name and the generic destination. This is also supported through an annotation as well as a messaging template.
By sending a message to /user/{username}/queue/something, it will be delivered only to the specific user identified by {username}.
Now, I'm looking for a solution that allows me to use an external Message Broker (for instance, RabbitMQ), with Spring just as Broker Relay:
registry.enableStompBrokerRelay("/topic/", "/queue/");
After configuring the External Message Broker in Spring:
Is it possible to send a message on Message Broker by using as channel /user/{username/}/queue/something? If yes, how?
By sending a message on Message Broker by using as channel /user/{username/}/queue/something, is Spring able to send that message only to {username} according to the current Principal?
Yes it is possible, if you enable an external broker, every #MessageMapping return value will be serialized to JSON and sent to the broker, have a look to the Flow of Messages section of the reference documentation for more details. So it is basically the same that with the simple broker.
You can also inject a SimpMessagingTemplate or SimpMessageSendingOperations bean, like it is done in my OpenSnap example application. You can use this from a Controller, but also from any other class in a pure push context.
You can retrieve the principal by adding a Principal parameter to your #MessageMapping or #SubscribeMapping handler method, like it is done here, the current principal will be automatically injected.
Related
I am implementing a simple Spring Boot application which has a certain number of users connected via the WebSocket protocol. The application sends personal statistics to each user every 5 seconds.
I am using the #SendToUser annotation to distinguish between users and personalize the statistics.
It seems to me that the method, annotated with #SendToUser, can be only used as a message-handling method. This means that in order to receive the statistics the user should first send the message to the Spring Boot application, then the message-handling method will be called, and only then the statistics will be sent back.
As a result, the WebSocket protocol does now look very useful: the request is still initiated by a user (by the user's browser, to be precise). The only advantage is that you should not open new connection every time you want to retrieve the fresh statistics.
Is it possible to get rid of the messages sent from the users to the application at all? For example, to retrieve the list of the currently connected users every 5 seconds, then iterate over the list and send the message to every user one by one? Or a user still needs to initiate the statistics retrieval?
You can inject SimpMessagingTemplate to any spring bean and send message to an user.
simpMessagingTemplate.convertAndSendToUser(username, "/destination", message);
See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/messaging/simp/SimpMessagingTemplate.html
You can retrieve list of the connected users by injecting SimpUserRegistry bean.
See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/messaging/simp/user/SimpUserRegistry.html
I'm trying to run example from http://www.baeldung.com/spring-remoting-amqp, even when I set up the connection to the dedicated vhost to my RabbitMQ broker, I can only send the request from client (I see it in RabbitMQ UI), but I never get the answer from the server.
The server seems to bean the service (the returning Impl class) with getBeanDefinitionNames(), but I definitly do not see those beans on the client side. I use annotations to set up beans, not the .xml file.
So the question is - why my client is not seeing the Server beans, I discover it more a less in following way:
#Autowired
private ApplicationContext appContext;
public GetResponse get(String id) {
Service service = appContext.getBean(Service.class);
System.out.println(service.ping());
return new GetResponse();
}
The answer which I get on the level of webservice is:
{
"timestamp": "2018-02-01T10:09:00.809Z",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.remoting.RemoteProxyFailureException",
"message": "No reply received from 'toString' with arguments '[]' - perhaps a timeout in the template?",
"path": "/v3/app/r"
}
Service:
public interface Service extends Serializable{
String ping();
}
Service Impl:
public class ServiceImpl implements Service {
#Override
public String ping() {
System.out.println("ponged");
return "pong";
}
#Override
public String toString() {
return "to string";
}
EDITED + BOUNTY
In the link you can find extracted modules which I want to connect together. I suppose that it is still about 'not seeing' the beans from one module in the second one.
The action can be trigerd with GET http://localhost:8081/v3/app/u The RabbitMQ settings has to be adjusted to your set-up.
https://bitbucket.org/herbatnic/springremotingexample/overview
I think you shouldn't set the routing key in your client, in amqpFactoryBean (and the one you set seems invalid):
https://bitbucket.org/herbatnic/springremotingexample/src/b1f08a5398889525a0b1a439b9bb4943f345ffd1/Mod1/src/main/java/simpleremoting/mod1/messaging/Caller.java?at=master&fileviewer=file-view-default
Did you try to run their example?
https://github.com/eugenp/tutorials/tree/master/spring-remoting/remoting-amqp
Just stumbled upon this question 3 years later.. trying to run the Baeldung example!
I tried debugging the issue and as far as I can tell, something internal in the AMQP implementation of spring remoting is not using the correct Routing Key when sending the client message, meaning the payload arrives at the broker and is never put into the queue for processing, we then timeout after 5s (default) on the client.
I tried the other answer by Syl to remove the routingKey however it doesn't seem to allow us to create a binding without one, and even when creating a binding directly on the broker management page (without a routing key) it doesn't route the messages.
I have not managed to make the example work, however I found a blog post on fatalerrors.org that shows a custom implementation of the AmqpProxyFactoryBean and it has custom handling for the routing key, this one works.
I've create this gist with the example that is working for me in case the blog post above goes under.
One other thing to note is that on the Baeldung example they are using a DirectExchange, while here we are using a TopicExchange.
I have a spring boot application that will have publish to user defined destination channels as such:
#Autowired
private SimpMessagingTemplate template;
public void send() {
//..
String uniqueId = "123";
this.template.convertAndSendToUser(uniqueId, "/event", "Hello");
}
Then a stomp over SockJS client can subscribe to it and receive the message. Suppose I have a stomp endpoint registered in my spring application called "/data"
var ws = new SockJS("/data");
var client = Stomp.over(ws);
var connect_fallback = function() {
client.subscribe("/user/123/event", sub_callback);
};
var sub_callback = function(msg) {
alert(msg);
};
client.connect('','', connect_callback);
Actually there will be more than one user client subscribing to the same distinct user destination, so each publish/subscribe channel is not one to one and I am only doing it this way since spring's concept of "/topic" have to be defined programmatically and "/queues" can only be consumed by one user. How do I know when a user destination no longer has any subscribers? And how do I delete a user destination?
#SendToUser('/queue/dest')
public String send() {
//..
String uniqueId = "123";
return "hello";
}
On the Client you would subscribe to '/user/queue/dest'
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-user-destination
After adding a channel interceptor and setting breakpoints within a code segment running in debug mode in eclipse, I found that there is a collection of websocket session and destination mappings in the registry objects that are held by the message handlers, which in turn can be seen stored inside the message channel object. I found this for the topics though not sure about purely user destination method.
However, Spring does not leave the api open for me to just call a function to get a list of all subscribers to every topic, at least not without passing in the message. Everything else that would have been helpful is set private so cannot be programmatically accessed. This is not helpful as i would like to trigger an action upon unsubscribe or disconnect of a client ONLY when the topic that is being unsubscribed/disconnected from does not have other listening clients left.
Now looking at embedding full featured broker such as ActiveMQ to see if it can potentially solve my problem
I have a rabbitmq listener as a separate class and JSF 2 managed bean.
In my bean I send a message and need to wait for result. I can't use sendAndReceive... because I send the message to one queue but receive from another queue, so I assign correlationId before sending.
So I need to wait asynchronously, I need to wait until right message comes to the listener. How to do it in rmq?
Looking at javadoc and source of RabbitTemplate it seems that he waits for response in reply queue. Do you set 'reply-to' property in your messages? If yes, then RabbitTemplate sendAndReceive methods should wait for response in 'reply-to' queue. Be sure to populate replyTo field correctly and test it.
Side note:
In RabbitMQ you do not send messages to the queue.
You send messages to the exchanges. Exchanges are routing messages to the queue(s) using bindings. With default or direct exchange type it looks like you send directly to the queue, but this is over-simplification.
See https://www.rabbitmq.com/tutorials/amqp-concepts.html for details.
Edit:
It seems there are some fix for that in AMQP 1.4.5.RELEASE
https://spring.io/blog/2015/05/08/spring-amqp-1-4-5-release-and-1-5-0-m1-available
Configurable Exchange/Routing Key for Replies
Previously, when using request/reply messaging with the
RabbitTemplate, replies were routed to the default exchange and routed
with the queue name. It is now possible to supply a reply-address with
the form exchange/routingKey to route using a specific exchange and
routing key.
I need to implement RPC over STOMP, where the client runs with javascript in a browser, and the server side is implemented using Spring messaging capabilities.
While using #MessageMapping is fine for normal messaging, I find using #SendToUser quite limitating for implementing RPC because the client has an hard time to understand which reply is associated with which request in a scenario when multiple simultaneous requests are being made from the client.
Of course there is no problem when just only one request is made, and the client waits for its reply, but problems arise when the client has to keep track of multiple "open" rpc calls.
I've managed to make the system mostly fine by associating an ID with every request, i.e.: the client sends an id together with the message, and the server replies with a special message wrapper that contains this id, so the client is able to associate asynchronous replies with requests.
This works fine but has several limitations:
I have to develop code that needs to understand this structure, and that defies the uitlity to have simple annotated methods
when the server side code generates an Exception the Spring #MessageExceptionHandler get called and the correct Exception is returned to the client, but the request id is lost because the handler has no (easy) way to access it.
I know that with rabbitmq we can add "reply-to" header to every request that needs to be associated with a special reply (the rpc response), and this is implemented by creating a special temporary queue that the user is automatically subscribed to, but how may I use this scheme in Spring? Also, that would tie me a specific broker.
How may I elegantly implement a correct RPC call in Spring that correctly handles server side exceptions?
I find this a general problem and I think Spring could benefit greatly to implement it natively.
This not exactly what you demand, but maybe you can attempt something like this :
Path variables in Spring WebSockets #SendTo mapping
You define an ID on your client and send id to the queue /user/queue/{myid}
On the serveur side you will have a class who looks like this :
#MessageMapping("/user/queue/{myid}")
public void simple(#DestinationVariable String id, Object requestDto) {
simpMessagingTemplate.convertAndSendToUser(userId, "/user/queue/" + id, responseDto);
}
This solution can work with the same principle as the rabbit mq solution you mention.
Hope this helps.
If you do not need the exception/reason on the client, but only want to know which message failed you could send ack messages for successful messages. For successful messages you always have easy access to the message id / headers. By the absence of the ack message the client knows which message has failed.
Of course this comes at the costs of sending all the ack messages and knowing the timout of requests. Also additional code is required to keep track on the client side, but this can be done using a middleware and would end up in an ok-ish dev experience for the business logic.