Search code examples
spring-bootwebsocketrabbitmqstomp

Spring boot stomp sends RECEIPT frame multiple times


I am developing a chat application with Stomp and Rabbitmq on Spring Boot.

When a client sends a message to the server (Spring Boot), the server saves the message to a database and then sends the message to its recipient.

What I would like to do is to send a RECEIPT frame with message-id to the clients when they send a message to let them know that their messages have been saved to the database.

I can send the RECEIPT frame from the afterMessageHandled() of ExecutorChannelInterceptor class. But it sends the same RECEIPT frame multiple times like three times.

So the clients receive the same RECEIPT frame three times for a single message.

This is my ChannelInterceptor which saves the message to the database in presend()

public class StompChannelInterceptor implements ChannelInterceptor {

@Autowired
private ChatService chatService;

@Autowired
private CompositeMessageConverter compositeMessageConverter;


@Override
public Message<?> preSend(Message<?> message, @NonNull MessageChannel channel) {
    MessageHeaders messageHeaders = message.getHeaders();
    StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.wrap(message);
    StompCommand stompCommand = stompHeaderAccessor.getCommand();

    if (StompCommand.SUBSCRIBE.equals(stompCommand)) {
        //validate subscribe
    } else if (StompCommand.SEND.equals(stompCommand)) {
        System.out.println("presend!!!!!!!!!!!!!!!!!!!");
        ChatMessageDTO chatMessageDTO =
                (ChatMessageDTO) compositeMessageConverter.fromMessage(message, ChatMessageDTO.class);
        chatService.validateAndSaveMessage(chatMessageDTO);
    }
    return message;
}
}

This is my MessageMapping function which handles the incoming messages

@MessageMapping("/chat/send")
public void send(@Payload ChatMessageDTO chatMessageDTO, MessageHeaders messageHeaders) {
    System.out.println("send!!!!!!!!!!!!!!!!!!!!!!!!!");
    simpMessagingTemplate.convertAndSend(queue, chatMessageDTO);
}

And lastly, this is my ExecutorChannelInterceptor which sends the RECEIPT frame in afterMessageHandled()

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.interceptors(chatChannelInterceptor());

    registration.interceptors(new ExecutorChannelInterceptor() {

        @Override
        public void afterMessageHandled(Message<?> inMessage,
                                        MessageChannel inChannel, MessageHandler handler, Exception ex) {

            StompHeaderAccessor inAccessor = StompHeaderAccessor.wrap(inMessage);

            if (StompCommand.SEND.equals(inAccessor.getCommand())) {

                System.out.println("afterMessageHandled");

                if (outChannel != null) {
                    StompHeaderAccessor outAccessor = StompHeaderAccessor.create(StompCommand.RECEIPT);
                    outAccessor.setSessionId(inAccessor.getSessionId());
                    outAccessor.setReceiptId(receipt);
                    outAccessor.setLeaveMutable(true);
                    outAccessor.setMessageId(inAccessor.getMessageId());

                    Message<byte[]> outMessage =
                            MessageBuilder.createMessage(new byte[0], outAccessor.getMessageHeaders());

                    outChannel.send(outMessage);
                }
            }
        }
    });
}

If I run this code, I have got this log, which indicates that afterMessageHandled() is called three times.

presend!!!!!!!!!!!!!!!!!!!
afterMessageHandled!!!!!!!!!!!!!!!!!!!!!
afterMessageHandled!!!!!!!!!!!!!!!!!!!!!
send!!!!!!!!!!!!!!!!!!!!!!!!!
afterMessageHandled!!!!!!!!!!!!!!!!!!!!!

So, questions are

  1. Is there any way to send a RECEIPT frame to clients only once right after presend() please because I'd like to send the RECEIPT frame after confirming that the message has been saved to the database which happens in presend()?

  2. Is it possible to change payload or headers in presend() please?


Solution

  • I found out that each call to afterHanldedMessage() comes with different handlers. So you can do what you want based on different handlers. You might want to do If(handler instanceof SomeHandlerClass).