Where the STOMP ACK frame goes to in RabbitMQ? - spring

I have an app where I send messages over Stomp subscriptions. When messages are received, they send ACK frame. I'd like to handle this ack frame on server side, and my question is there any ack listener in rabbitmq or ack handler that will takes that frame and its headers? I'd like to access that frame to send some kind of confirmation to the producer that message was received.
stompClient.subscribe('/topic/'+ thread, function (greeting) {
var divId = "#userinfo";
console.log("SENDING TO TOPIC/GREETING")
showGreeting(divId, JSON.parse(greeting.body).person,
JSON.parse(greeting.body).message);
greeting.ack();
}, {id: thread, ack:'client'});
#MessageMapping("/message")
public MessageTemplate send(MessageTemplate messageTemp) throws Exception {
MessageTemplate message = new MessageTemplate(messageTemp.getPerson(), messageTemp.getMessage(), messageTemp.getTo());
rabbitTemplate.convertAndSend("amq.topic", messageTemp.getTo(), message);
return message;
}
<<< MESSAGE
subscription:other
destination:/topic/other
message-id:T_other##session-8-X11WtIiVQr-7UcONamng##3
redelivered:false
__TypeId__:com.patrykmaryn.spring.second.springsecond.MessageTemplate
priority:0
persistent:true
content-encoding:UTF-8
content-type:application/json
content-length:46
>>> ACK
message-id:T_other##session-8-X11WtIiVQr-7UcONamng##3
subscription:other
EDIT
I thought I could use
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(inBoundInterceptor);
}
And then in
public class InboundMessagesChannelInterceptor implements ChannelInterceptor {
#Override
public Message<?> preSend(Message<?> message, MessageChannel messageChannel) {
// here's some logic to do with ACK frame like sending a
// notifiction message to the sender
return message;
}
}
However the method intercepts each inbound event, but I'd need only ACK message. In this case how could I get the ACK frame headers only?

The STOMP protocol has no support for telling a sender that their message has been acknowledged therefore you're going to need to implement your solution at the application level rather than at the protocol level. You can do this by implementing a request/response pattern in your sender and consumer.
This will, of course, significantly increase the amount of communication which your clients conduct which could potentially limit your application's scalability, but there's no way to avoid the communication overhead if you want this functionality with STOMP.

Related

spring amqp (rabbitmq) and sending to DLQ when exception occurs

I am using org.springframework.boot:spring-boot-starter-amqp:2.6.6 .
According to the documentation, I set up #RabbitListener - I use SimpleRabbitListenerContainerFactory and the configuration looks like this:
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ObjectMapper om) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factory.setConcurrentConsumers(rabbitProperties.getUpdater().getConcurrentConsumers());
factory.setMaxConcurrentConsumers(rabbitProperties.getUpdater().getMaxConcurrentConsumers());
factory.setMessageConverter(new Jackson2JsonMessageConverter(om));
factory.setAutoStartup(rabbitProperties.getUpdater().getAutoStartup());
factory.setDefaultRequeueRejected(false);
return factory;
}
The logic of the service is to receive messages from rabbitmq, contact an external service via the rest API (using rest template) and put some information into the database based on the results of the response (using spring data jpa). The service implemented it successfully, but during testing it ran into problems that if any exceptions occur during the work of those thrown up the stack, the message is not sent to the configured dlq, but simply hangs in the broker as unacked. Can you please tell me how you can tell spring amqp that if any error occurs, you need to redirect the message to dlq?
The listener itself looks something like this:
#RabbitListener(
queues = {"${rabbit.updater.consuming.queue.name}"},
containerFactory = "rabbitListenerContainerFactory"
)
#Override
public void listen(
#Valid #Payload MessageDTO message,
Channel channel,
#Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag
) {
log.debug(DebugMessagesConstants.RECEIVED_MESSAGE_FROM_QUEUE, message, deliveryTag);
messageUpdater.process(message);
channel.basicAck(deliveryTag, false);
log.debug(DebugMessagesConstants.PROCESSED_MESSAGE_FROM_QUEUE, message, deliveryTag);
}
In rabbit managment it look something like this:
enter image description here
and unacked will hang until the queue consuming application stops
See error handling documentation: https://docs.spring.io/spring-amqp/docs/current/reference/html/#annotation-error-handling.
So, you just don't do an AcknowledgeMode.MANUAL and rely on the Dead Letter Exchange configuration for those messages which are rejected in case of error.
Or try to use a this.channel.basicNack(deliveryTag, false, false) in case of messageUpdater.process(message); exception...

Is there a way to persist the last message received on a web socket disconnection in Spring?

I have a web socket connection established between a browser client and a Spring Boot backend using STOMP and sock js. Every second, a payload is sent to the server from the client containing data that needs to be persisted to a Postgres database. There may be thousands of clients connected simultaneously so I don't want to update the database every second for each one. So, to reduce the CPU load, I want to listen for when a web socket StompCommand.DISCONNECT event occurs and then persist the last received message from the client.
Is this possible, or is there another way to get around thi sproblem?
In this case - the question is really opinionated - there are many possible implementations.
One of the implementations can do the following:
When you receive the message from the connected client - maintain a map (in memory will be enough, for the sake of idea) of the the identifier of the current client to the Last Data.
Every time you get a new message in #MessageMapping annotated class - update an entry in the map, so that it will always contain the last message.
The value of the map will be the last message, the key can be Principal, SessionId string - whatever you'll find useful.
#Component
public class LastMessageHolder {
private Map<Principal, MyData> lastDataPerPrincipal;
public void updateLastData(Principal principal, MyData data) {
lastDataPerPrincipal.put(principal, data);
}
public MyData getLastDataForPrincipalAndClear(Principal principal) {
return lastDataPerPrincipal.remove(principal);
}
}
The message Receiver will get the messages through the stomp channel and update the last message holder
#Component
public class MyMessageReceiver {
#Autowired
private LastMessageHolder lastMessageHolder;
#MessageMapping(...)
public void onDataReceived(Principal principal, MyData data) {
// this gets called every second per client
lastMessageHolder.updateLastData(principal, data);
}
}
And when you listen for the disconnect message in the channel interceptor - make remove the data from the principal that is being disconnected and store it in the database:
#Component
public class DbStoreChannelInterceptor implements ChannelInterceptor {
#Autowired
private LastMessageHolder lastMessageHolder;
#Autowired // something that will store your stuff in the db
private DbDao dbDao;
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message,
StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// populate a principal here, from headers, authentication token,
whatever
Principal principal = ...
accessor.setUser(principal);
}
if (StompCommand.DISCONNECT.equals(accessor.getCommand())) {
Principal principal = accessor.getUser();
MyData data = lastMessageHolder.getDataForPrincipalAndClear(principal);
dbDao.storeDataInDbForPrincipal(principal, data);
}
}
}
This is a basic idea.
From that you can take it further, and instead of storing the data from the channel interceptor (in this case the actual INSERT will be done for each client) you might want to throw it into some in-memory or distributed queue - whatever suits you best, so that the consumer will read the batch of the data objects and store them all at once, so that it will lead to much less load on your RDBMS.
In addition, I'll just mention, that you should think about the situation where the client keeps sending the data, but the server gets down for some reason, while the client is still interested to keep sending data. This is more in the area of the architecture of the distributed system, so its way beyond the scope of the question.

Request response over HTTP with Spring and activemq

I am building a simple REST api which connects a web server to a back end service, which performs a simple check and sends a response.
So client (over HTTP) -> to Web Server (over ACTIVEMQ/CAMEL)-> to Checking-Service, and back again.
The endpoint for the GET request is "/{id}". I'm trying to make this send a message through queue:ws-out to queue:cs-in and map it all the way back again to the original GET request.
The Checking-Service (cs) code is fine, it simply changes a value in the CheckMessage object to true using jmslistener.
I've searched the web thoroughly for examples, but can't get anything to work. The closest one I found was the following.
This is what I have so far on the Web Server (ws).
RestController
import ...
#RestController
public class RESTController extends Exception{
#Autowired
CamelContext camelContext;
#Autowired
JmsTemplate jmsTemplate;
#GetMapping("/{id}")
public String testCamel(#PathVariable String id) {
//Object used to send out
CheckMessage outMsg = new CheckMessage(id);
//Object used to receive response
CheckMessage inMsg = new CheckMessage(id);
//Sending the message out (working)
jmsTemplate.convertAndSend("ws-out", outMsg);
//Returning the response to the client (need correlation to the out message"
return jmsTemplate.receiveSelectedAndConvert("ws-in", ??);
}
}
Listener on ws
#Service
public class WSListener {
//For receiving the response from Checking-Service
#JmsListener(destination = "ws-in")
public void receiveMessage(CheckMessage response) {
}
}
Thanks!
your receive messages from "ws-in" with 2 consumers jmsTemplate.receiveSelectedAndConvert and WSListener !! message from a queue is consumed by one of the 2.
you send messages to "ws-out" and consume from "ws-in" ?? last queue
is empty and not receive any message, you have to send messages to
it
you need a valid selector to retrieve the message with receiveSelectedAndConvert based on JMSCorrelationID as the example you mntioned or the id received from the rest request but you need to add this id to the message headers like below
this.jmsTemplate.convertAndSend("ws-out", id, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
TextMessage tm = session.createTextMessage(new CheckMessage(id));
tm.setJMSCorrelationID(id);
return tm;
}
});
return jmsTemplate.receiveSelectedAndConvert("ws-in", "JMSCorrelationID='" + id+ "'");
forward messages from "ws-out" to "ws-in"
#Service
public class WSListener {
//For receiving the response from Checking-Service
#JmsListener(destination = "ws-out")
public void receiveMessage(CheckMessage response) {
jmsTemplate.convertAndSend("ws-in", response);
}
}

Spring send message to Websocket Message Broker

I want to send a message to websocket subscribers of a specific record - when an action takes place in one of my service class.
I'm trying to read the Spring Websocket documentation but it's kind of ambiguous to the point of how to get all these things working together.
Here are my setup files (this is extending jHipster btw):
WebsocketConfiguration.java
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/queue/", "/topic/", "/exchange/");
config.setApplicationDestinationPrefixes("/app");
config.setPathMatcher(new AntPathMatcher("."));
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
WebsocketSecurity.java
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
// message types other than MESSAGE and SUBSCRIBE
.nullDestMatcher().authenticated()
// matches any destination that starts with /rooms/
.simpDestMatchers("/topic/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.simpDestMatchers("/topic/**").authenticated()
// (i.e. cannot send messages directly to /topic/, /queue/)
// (i.e. cannot subscribe to /topic/messages/* to get messages sent to
// /topic/messages-user<id>)
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()
// catch all
.anyMessage().denyAll();
}
Controller class (attempt at implementing a simple broker I can test subscribing to from sockjs and recieving messages generated elsewhere in the application:
#MessageMapping("/ws")
#SendTo("/topic/sendactivity.{id}")
public void activity(#DestinationVariable string id, #Payload String message){
log.debug("Sending command center: "+message);
}
#RequestMapping(value = "/updateactivity", method = RequestMethod.PUT)
public ResponseEntity<Membership> updateMembership(
#RequestBody Membership membership) throws URISyntaxException {
// ...
String testString = "test";
messagingTemplate.convertAndSend("/topic/commandcenter"+membership.getId().toString(), testString);
// ...
}
When I put a breakpoint on the public void activity method, I don't get anything?
Sending a message to "/topic/commandcenterID" using the messaging template will send that message to the message broker, which will dispatch that message to clients subscribed to that topic. So it won't flow through your activity method.
When using #MessageMapping annotated methods, you're declaring those as application destinations. So sending a message to "/app/ws" should map to that method. Note that in that case I doubt it'll work since the destination variable you're expecting as a method argument is missing from the path definition in the #MessageMapping annotation.
Also, the #SendTo annotation in fact tells Spring that the value returned by the method should be converted to a message and sent to the given destination.
It seems you're mixing things up here, and I think you should:
read carefully the flow of messages in Spring STOMP support
look at a few example apps like the websocket portfolio and websocket chat

How to broadcast a message using raw Spring 4 WebSockets without STOMP?

In this great answer https://stackoverflow.com/a/27161986/4358405 there is an example of how to use raw Spring4 WebSockets without STOMP subprotocol (and without SockJS potentially).
Now my question is: how do I broadcast to all clients? I expected to see an API that I could use in similar fashion with that of pure JSR 356 websockets API: session.getBasicRemote().sendText(messJson);
Do I need to keep all WebSocketSession objects on my own and then call sendMessage() on each of them?
I found a solution. In the WebSocket handler, we manage a list of WebSocketSession and add new session on afterConnectionEstablished function.
private List<WebSocketSession> sessions = new ArrayList<>();
synchronized void addSession(WebSocketSession sess) {
this.sessions.add(sess);
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
addSession(session);
System.out.println("New Session: " + session.getId());
}
When we need to broadcast, just enumerate through all session in list sessions and send messages.
for (WebSocketSession sess : sessions) {
TextMessage msg = new TextMessage("Hello from " + session.getId() + "!");
sess.sendMessage(msg);
}
Hope this help!
As far as i know and can gather from the documentation here you can't broadcast using the WebSocketHandler.
Instead you should use Stomp over WebSocket configured by a WebSocketMessageBrokerConfigurer as described here.
Use a SimpMessagingTemplate anywhere in your code to send messages to subscribed clients as described here

Resources