How to Implement RabbitMQ consumer part(receiver ) inside the Spring boot API - java

I am going to do send my DATA toRabbitMq producer(message sender) and get responsible data from RabbitMq consumer(message receiver). producer part is working fine .now my problem is how to implement consumer part (receiver part) in side the Spring boot API. .Below is My spring boot API and i written ProducerAndConsumer one class.
ProducerAndConsumer.class
#Component
public class ProducerAndConsumer {
#Autowired
private RabbitTemplate rabbitTemplate;
//MessageProducer part (send part)
public boolean sendMessage(String message) {
rabbitTemplate.convertAndSend(RobbitMqConfig.ROUTING_KEY, message);
System.out.println("Is listener returned ::: ==========="+rabbitTemplate.isReturnListener());
return rabbitTemplate.isReturnListener();
}
//Consumer part (receiver part)
#RabbitListener(queues = RobbitMqConfig.QUEUE_NAME1)
public void receiveMessage ( final Message message){
System.out.println("Received message====Receiver=====" + message.getPayload());
}
}
API part
#PostMapping(value = {"/sendFilesName"})
public ResponseEntity<?> sendFilesName(#RequestBody SendFileNameRequest sendFileNameRequest, HttpServletRequest request) throws ParseException {
System.out.println("FileNameArray="+sendFileNameRequest.getFileNameArray());
if(sendFileNameRequest.getFileNameArray().size()!=0) {
List<String> message = sendFileNameRequest.getFileNameArray();
**//see here i send my message array data**
if(producerAndConsumer.sendMessage(message.toString())){
**//here i want implement my receiver part how to?**
return ResponseEntity.ok(new ApiResponse(true, "fileName List sent successfully", "",true));
}else {
return ResponseEntity.ok(new ApiResponse(false, "fileName List sent Fails", "",true));
}
}else {
return ResponseEntity.ok(new ApiResponse(false, "fileName List not present ", "",true));
}
}

The routing algorithm behind a direct exchange is simple - a message goes to the queues whose binding key exactly matches the routing key of the message.
spring amqp
Note: Check the routing key and queues binded using rabbitmq admin console to figure out whats going on or share the rabbitmq configuration.

Related

How to process messages from Spring Boot #RabbitListener in different handlers?

I'm trying to use request/response pattern for Spring Boot using AMQP and Spring-web. I have client service that has #RestController and AsyncRabbit configuration with Direct Exchange, Routing key etc. and server that has simple listener for request queue.
Client (something like rest gateway controller):
#RestController
public class ClientController {
#GetMapping("/test1")
public String getRequest() {
ListenableFuture<String> listenableFuture = asyncRabbitTemplate.convertAndReceiveAsType(
directExchange.getName(),
routingKey,
testDto,
new ParameterizedTypeReference<>() {}
);
return listenableFuture.get(); // Here I receive response from server services
}
#GetMapping("/test2")
public String getRequest2() {
ListenableFuture<String> listenableFuture = asyncRabbitTemplate.convertAndReceiveAsType(
/* Same properties but I use another DTO object for request */
);
return listenableFuture.get()
}
Server:
#RabbitListener(queues = "#{queue.name}", concurrency = "10")
#Component
public class Consumer {
#RabbitHandler
public String receive(TestDto testDto) {
...
}
#RabbitHandler
public String receive2(AnotherTestDto anotherTestDto) {
...
}
}
How should I implement Rabbit listener to process each REST request?
I found only two ways to do that:
Using #RabbitHandler as in the example above. But for each request method (GET, POST, etc.) I need unique DTO class to send message and process it in correct handler even if "request body" is almost same (number of request methods = number of DTO class to send). I'm not sure that is right way.
Leave one Consumer and call desired method to process that depends on message body (trivial if-else):
...
#RabbitListener(queues = "#{queue.name}")
public String receive(MessageDto messageDto) {
if (messageDto.requestType == "get.method1") {
return serverService.processThat(messageDto);
} else if (messageDto.requestType == "post.method2") {
return serverService.processAnother(messageDto);
} else if ...
...
}
...
But add new if-else branch every time is not very convenient so I really out of ideas.
You may consider to use different queues for different request types. All of them are going to be bound to the same direct exchange, but with their respective routing key.
What you would need on the consumer side is just to add a new #RabbitListener for respective queue. And bind that queue to the exchange with its routing key.
That's actually a beauty of the AMQP protol by itself: the producer always publish to the same exchange with respective routing key. The consumer registers its interest for routing keys and binds a queue. The rest of routing logic is done on the AMQP broker.
See more info in docs: https://www.rabbitmq.com/tutorials/tutorial-four-spring-amqp.html

RabbitMQ Spring Java - RPC Pattern

I have some Spring applications that communicate between them using RabbitMQ as broker. I can send and receive messages asynchronously between them. But now, I need one application to send a message to another one and wait for the response. So, for this I am trying to implement the RPC pattern. It is working, but the problem is that I could only do it using temporary queues generated by Spring.
https://www.rabbitmq.com/tutorials/tutorial-six-spring-amqp.html
This is the code that sends the message and wait for the response.
public void send() {
....
Integer response = (Integer) template.convertSendAndReceive(exchange.getName(), "rpc", "message");
...
}
When I send the message, the execution is blocked until the response is received and a temporary queue is created by Spring for the response, as expected.
But what I need is to use a specific and fixed queue, defined by me, to receive the responses. I need responses to be sent to an exchange with a routing key pointing to the fixed response queue (doing this I'll be able to send the responses to another queue too, that will be logging all responses).
I tried setting the "setReplyTo" property to the message, but is not working.
What version are you using? With modern versions, direct reply_to is used by default, but you can revert to using a temporary queue by setting a property on the template.
https://docs.spring.io/spring-amqp/docs/current/reference/html/#direct-reply-to
To use a named reply queue, see the documentation about how to set up a reply container, with the template as the message listener:
https://docs.spring.io/spring-amqp/docs/current/reference/html/#message-correlation-with-a-reply-queue
and
https://docs.spring.io/spring-amqp/docs/current/reference/html/#reply-listener
EDIT
The template will block until the corresponding reply is passed into it by the reply container (or it times out).
#SpringBootApplication
public class So68986604Application {
public static void main(String[] args) {
SpringApplication.run(So68986604Application.class, args);
}
#RabbitListener(queues = "foo")
public String listen(String in) {
System.out.println(in);
return in.toUpperCase();
}
#Bean
Queue foo() {
return new Queue("foo");
}
#Bean
Queue replies() {
return new Queue("foo.replies");
}
#Bean
SimpleMessageListenerContainer replyContainer(ConnectionFactory cf, RabbitTemplate template) {
SimpleMessageListenerContainer replyer = new SimpleMessageListenerContainer(cf);
replyer.setQueueNames("foo.replies");
replyer.setMessageListener(template);
template.setReplyAddress("foo.replies");
return replyer;
}
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
return args -> {
System.out.println(template.convertSendAndReceive("", "foo", "test"));
};
}
}
test
TEST

How to handle socket disconnection and heartbeat messages?

What I am trying to do
I have a lobby with players and when someone leaves the lobby I want to update it for every client so the actual list of players is displayed.
What I have done
To avoid cyclical requests being sent from frontend to backend I decided to use web sockets. When someone leaves the lobby then request is sent to REST api and then backend, upon receiving this request, does all the business logic and afterwards "pokes" this lobby using socket in order to update all clients in the lobby.
My problem
Everything works fine except the case when user closes the browser or the tab because I can't send a request in this scenario. (as far as I know this is impossible to do using javascript and beforeunload event, onDestroy() methods, etc..)
My question
Is it possible to check on the server side whether any socket disconnected and if yes then how can I do this? I also tried to use heartbeat which is being sent from frontend to backend but I don't know how to handle this heartbeat message on the server side.
Server side (Spring boot)
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguartion implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
ThreadPoolTaskScheduler te = new ThreadPoolTaskScheduler();
te.setPoolSize(1);
te.setThreadNamePrefix("wss-heartbeat-thread-");
te.initialize();
config.enableSimpleBroker("/lobby")
.setHeartbeatValue(new long[]{0, 1000})
.setTaskScheduler(te);
}
}
#Controller
public class WebSocketController {
private final SimpMessagingTemplate template;
WebSocketController(SimpMessagingTemplate template) {
this.template = template;
}
public void pokeLobby(#DestinationVariable String lobbyName, SocketMessage message) {
this.template.convertAndSend("/lobby/"+lobbyName.toLowerCase(), message);
}
}
Client side
connectToLobbyWebSocket(lobbyName: string): void {
const ws = new SockJS(this.addressStorage.apiAddress + '/socket');
this.stompClient = Stomp.over(ws);
// this.stompClient.debug = null;
const that = this;
this.stompClient.connect({}, function () {
that.stompClient.subscribe('/lobby/' + lobbyName, (message) => {
if (message.body) {
that.socketMessage.next(message.body); // do client logic
}
});
});
}
You can listen for SessionDisconnectEvent in your application and send messages to other clients when you receive such an event.
Event raised when the session of a WebSocket client using a Simple Messaging Protocol (e.g. STOMP) as the WebSocket sub-protocol is closed.
Note that this event may be raised more than once for a single session and therefore event consumers should be idempotent and ignore a duplicate event.
There are other types of events also.

Spring+WebSocket+STOMP. Message to specific session (NOT user)

I am trying to set up basic message broker on Spring framework, using a recipe I found here
Author claims it has worked well, but I am unable to receive messages on client, though no visible errors were found.
Goal:
What I am trying to do is basically the same - a client connects to server and requests some async operation. After operation completes the client should receive an event. Important note: client is not authenticated by Spring, but an event from async back-end part of the message broker contains his login, so I assumed it would be enough to store concurrent map of Login-SessionId pairs for sending messages directly to particular session.
Client code:
//app.js
var stompClient = null;
var subscription = '/user/queue/response';
//invoked after I hit "connect" button
function connect() {
//reading from input text form
var agentId = $("#agentId").val();
var socket = new SockJS('localhost:5555/cti');
stompClient = Stomp.over(socket);
stompClient.connect({'Login':agentId}, function (frame) {
setConnected(true);
console.log('Connected to subscription');
stompClient.subscribe(subscription, function (response) {
console.log(response);
});
});
}
//invoked after I hit "send" button
function send() {
var cmd_str = $("#cmd").val();
var cmd = {
'command':cmd_str
};
console.log("sending message...");
stompClient.send("/app/request", {}, JSON.stringify(cmd));
console.log("message sent");
}
Here is my configuration.
//message broker configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/** queue prefix for SUBSCRIPTION (FROM server to CLIENT) */
config.enableSimpleBroker("/topic");
/** queue prefix for SENDING messages (FROM client TO server) */
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/cti")
.setAllowedOrigins("*")
.withSockJS();
}
}
Now, after basic config I should implement an application event handler to provide session-related information on client connect.
//application listener
#Service
public class STOMPConnectEventListener implements ApplicationListener<SessionConnectEvent> {
#Autowired
//this is basically a concurrent map for storing pairs "sessionId - login"
WebAgentSessionRegistry webAgentSessionRegistry;
#Override
public void onApplicationEvent(SessionConnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
String agentId = sha.getNativeHeader("Login").get(0);
String sessionId = sha.getSessionId();
/** add new session to registry */
webAgentSessionRegistry.addSession(agentId,sessionId);
//debug: show connected to stdout
webAgentSessionRegistry.show();
}
}
All good so far. After I run my spring webapp in IDE and connected my "clients" from two browser tabs I got this in IDE console:
session_id / agent_id
-----------------------------
|kecpp1vt|user1|
|10g5e10n|user2|
-----------------------------
Okay, now let's try to implement message mechanics.
//STOMPController
#Controller
public class STOMPController {
#Autowired
//our registry we have already set up earlier
WebAgentSessionRegistry webAgentSessionRegistry;
#Autowired
//a helper service which I will post below
MessageSender sender;
#MessageMapping("/request")
public void handleRequestMessage() throws InterruptedException {
Map<String,String> params = new HashMap(1);
params.put("test","test");
//a custom object for event, not really relevant
EventMessage msg = new EventMessage("TEST",params);
//send to user2 (just for the sake of it)
String s_id = webAgentSessionRegistry.getSessionId("user2");
System.out.println("Sending message to user2. Target session: "+s_id);
sender.sendEventToClient(msg,s_id);
System.out.println("Message sent");
}
}
A service to send messages from any part of the application:
//MessageSender
#Service
public class MessageSender implements IMessageSender{
#Autowired
WebAgentSessionRegistry webAgentSessionRegistry;
#Autowired
SimpMessageSendingOperations messageTemplate;
private String qName = "/queue/response";
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
#Override
public void sendEventToClient(EventMessage event,String sessionId) {
messageTemplate.convertAndSendToUser(sessionId,qName,event,createHeaders(sessionId));
}
}
Now, let's try to test it. I run my IDE, opened Chrome and created 2 tabs form which I connected to server. User1 and User2. Result console:
session_id / agent_id
-----------------------------
|kecpp1vt|user1|
|10g5e10n|user2|
-----------------------------
Sending message to user2. Target session: 10g5e10n
Message sent
But, as I mentioned in the beginning - user2 got absolutely nothing, though he is connected and subscribed to "/user/queue/response". No errors either.
A question is, where exactly I am missing the point? I have read many articles on the subject, but to no avail.
SPR-11309 says it's possible and should work. Maybe, id-s aren't actual session id-s?
And well maybe someone knows how to monitor if the message actually has been sent, not dropped by internal Spring mechanics?
SOLUTION UPDATE:
A misconfigured bit:
//WebSocketConfig.java:
....
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/** queue prefix for SUBSCRIPTION (FROM server to CLIENT) */
// + parameter "/queue"
config.enableSimpleBroker("/topic","/queue");
/** queue prefix for SENDING messages (FROM client TO server) */
config.setApplicationDestinationPrefixes("/app");
}
....
I've spent a day debugging internal spring mechanics to find out where exactly it goes wrong:
//AbstractBrokerMessageHandler.java:
....
protected boolean checkDestinationPrefix(String destination) {
if ((destination == null) || CollectionUtils.isEmpty(this.destinationPrefixes)) {
return true;
}
for (String prefix : this.destinationPrefixes) {
if (destination.startsWith(prefix)) {
//guess what? this.destinationPrefixes contains only "/topic". Surprise, surprise
return true;
}
}
return false;
}
....
Although I have to admit I still think the documentation mentioned that user personal queues aren't to be configured explicitly cause they "already there". Maybe I just got it wrong.
Overall it looks good, but could you change from
config.enableSimpleBroker("/topic");
to
config.enableSimpleBroker("/queue");
... and see if this works? Hope this help.

Send STOMP ERROR from Spring Websocket program

I have a Spring Websocket Stomp application that accepts SUBSCRIBE requests.
In application I have a handler for SUBSCRIBE, that is,
#Component
public class SubscribeStompEventHandler implements ApplicationListener<SessionSubscribeEvent> {
#Override
public void onApplicationEvent(SessionSubscribeEvent event) {}
}
that I use to validate subscription.
I would check something in the onApplicationEvent and send STOMP ERROR message back to client from this function.
I found this recipe How to send ERROR message to STOMP clients with Spring WebSocket? but I need to understand how to get outboundChannel.
I tried also the following code:
public void sendStompError(SimpMessagingTemplate simpMessagingTemplate, String sessionId, String topic, String errorMessage) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.ERROR);
headerAccessor.setMessage(errorMessage);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
simpMessagingTemplate.convertAndSendToUser(sessionId, topic, new byte[0], headerAccessor.getMessageHeaders());
}
and I tried topic to be some subsciption topic and /queue/error topic. However I did not see messages propagating to client.
In Client, I use:
stompClient.connect(headers
, function (frame) {
console.log("Conn OK " + url);
}, function (error) {
console.log("Conn NOT OK " + url + ": " + JSON.stringify(error));
});
}
and my goal is to have function(error) called when I send STOMP ERROR.
Please advice me how exactly I can send proper STOMP ERROR, e.g. by getting Outboundchannel.
You can send ERROR Message like this:
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.ERROR);
headerAccessor.setMessage(error.getMessage());
headerAccessor.setSessionId(sessionId);
this.clientOutboundChannel.send(MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders()));
The following is just enough to inject that clientOutboundChannel:
#Autowired
#Qualifier("clientOutboundChannel")
private MessageChannel clientOutboundChannel;
Just because clientOutboundChannel bean is declared in the AbstractMessageBrokerConfiguration.
UPDATE
STOMP ERROR always closes connection? I am getting this effect. Code 1002.
Yes, it is. See StompSubProtocolHandler.sendToClient():
if (StompCommand.ERROR.equals(command)) {
try {
session.close(CloseStatus.PROTOCOL_ERROR);
}
catch (IOException ex) {
// Ignore
}
}

Categories

Resources