Multiple rooms in Spring using STOMP - spring

Is it possible to create rooms with STOMP and Spring 4? Socket.IO has rooms built in, so I'm wondering if Spring has this
My code at the moment:
#MessageMapping("/room/greet/{room}")
#SendTo("/room/{room}")
public Greeting greet(#DestinationVariable String room, HelloMessage message) throws Exception {
return new Greeting("Hello, " + room + "!");
}
It would be ideal to have #SendTo("/room/{room}")
however, I am limited to:
#SendTo("/room/room1")
#SendTo("/room/room2")
#SendTo("/room/room3")
etc...which is VERY VERY unideal
The client is:
stompClient.subscribe('/room/' + roomID, function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
where roomID can be room1, room2, or room3...
What If I want more rooms? It feels like such a pain right now

It looks like this "room" feature is actually a publish/subscribe mechanism, something achieved with topics in Spring Websocket support (see STOMP protocol support and destinations for more info on this).
With this example:
#Controller
public class GreetingController {
#MessageMapping("/room/greeting/{room}")
public Greeting greet(#DestinationVariable String room, HelloMessage message) throws Exception {
return new Greeting("Hello, " + message.getName() + "!");
}
}
If a message is sent to "/room/greeting/room1", then the return value Greeting will be automatically sent to "/topic/room/greeting/room1", so the initial destination prefixed with "/topic".
If you wish to customize the destination, you can use #SendTo just like you did, or use a MessagingTemplate like this:
#Controller
public class GreetingController {
private SimpMessagingTemplate template;
#Autowired
public GreetingController(SimpMessagingTemplate template) {
this.template = template;
}
#MessageMapping("/room/greeting/{room}")
public void greet(#DestinationVariable String room, HelloMessage message) throws Exception {
Greeting greeting = new Greeting("Hello, " + message.getName() + "!");
this.template.convertAndSend("/topic/room/"+room, greeting);
}
}
I think taking a quick look at the reference documentation and some useful examples, such as a portfolio app and a chat app should be useful.

You can use netty socket the implementation of socket io in java

Related

Spring Cloud Stream - Consume Data on Demand manually?

Using Spring Data Stream, How can I start reading from a queue and stop reading on demand?
I want to so something like this:
#EnableBinding(Sink.class)
public class SomeConsumer {
#StreamListener(target = Sink.INPUT)
public void receiveMsg(Message<String> message)
{
logger.info(" received new message [" + message.toString() + "] ");
}
public static void startReceiving()
{
//How to implement this logic?
}
public static void stopReceiving()
{
//How to implement this logic?
}
}
It can't be done in a static method; autowire the BindingsEndpoint and use the changeState() method.
See my answer to this question.

Using websockets in spring, how do I send multiple updating messages?

In the example given here https://spring.io/guides/gs/messaging-stomp-websocket/ , one receives data and then returns a second later with another data structure. How would I amend this to send multiple data elements if (for example) the server is processing data ?
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
// I would like to send initial data here
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
// I would like to send more data here (after a bit more server side processing)
}```
(in kotlin). Argh.. this does not work - it sends it to all clients
#Controller
public class GreetingController() {
var template: SimpMessagingTemplate? = null
#Autowired
constructor(_template: SimpMessagingTemplate) : this() {
this.template = _template
}
#MessageMapping("/hello")
fun greet(greeting: String) {
repeat(5) { i ->
this.template!!.convertAndSend("/topic/greetings", Greeting("Iteration $i"));
Thread.sleep(1000)
}
}
}

Why isnt my sockets onsubscribe event getting used?

I am using java springboot with maven in order to get the spring boot starter socket package. My clients are using angular with stompjs and sockjs-client.
I am trying to set up a simple web socket application that allows for multiple rooms based on a roomId. When a client joins a room they should receive the last five messages sent in that room.
My Springboot app has three classes, the basic Application.java that I use to run the app, a web socket config class and a web socket controller:
#Controller
public class WebSocketController {
private final SimpMessagingTemplate template;
#Autowired
WebSocketController(SimpMessagingTemplate template){
this.template = template;
}
#MessageMapping("/meeting/{roomId}")
private void sendMessageTpPrivateRoom(
String message,
#DestinationVariable String roomId
) throws IOException {
System.out.println("message sent to: " + roomId);
this.template.convertAndSend("/meeting/" + roomId, message);
addToHistory(roomId, message);
}
#SubscribeMapping("/meeting/{roomId}")
public String chatInit(#DestinationVariable String roomId) {
System.out.println("Someone joined room: " + roomId);
return getLastFiveMessages(roomId);
}
}
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration
extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/meeting");
}
}
my clients are subscribing to the socket like so:
stompClient.subscribe(`app/meeting/${roomId}`, (message) => {
if (message.body) {
console.log(message.body);
messages += '<br>' + message.body;
}
});
and sending messages like so:
this.stompClient.send(`/app/meeting/${this.roomId}` , {}, message);
The message sending and handling is working great, when I set up three clients, two in room one, and one in room two, the room two messages are not being seen in room one and the room one messages are seen by both clients.
However the on subscribe event is not firing no matter what room I join. It is very necessary that when a client joins room one, they should receive some sort of history of that room. Any advice as to why my SubscribeMapping method is not being triggered when a client subscribes to the room?
The /meeting part will be implicitly added to URL you provide when subscribing. So your mapping will look like this:
#SubscribeMapping("/${roomId}")
public String chatInit(#DestinationVariable String roomId) {
System.out.println("Someone joined room: " + roomId);
return getLastFiveMessages(roomId);
}
Source: https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html

Sending a Message with Spring Cloud Stream and RabbitMq changes ID

I'm using Spring Cloud Stream and RabbitMq to exchange Messages between different microservices.
Thats my setup to publish a message.
public interface OutputChannels {
static final String OUTPUT_CHANNEL = "outputChannel";
#Output
MessageChannel outputChannel();
}
.
#EnableBinding(OutputChannels.class)
#Log4j
public class OutputProducer {
#Autowired
private OutputChannels outputChannels;
public void createMessage(MyContent myContent) {
Message<MyContent> message = MessageBuilder
.withPayload(myContent)
.build();
outputChannels.outputChannel().send(message);
log.info("Sent message: " + message.getHeaders().getId() + myContent);
}
}
And the setup to receive the message
public interface InputChannels {
String INPUT_CHANNEL = "inputChannel";
#Input
SubscribableChannel inputChannel();
}
.
#EnableBinding(InputChannels.class)
#Log
public class InputConsumer {
#StreamListener(InputChannels.INPUT_CHANNEL)
public void receive(Message<MyContent> message) {
MyContent myContent = message.getPayload();
log.info("Received message: " + message.getHeaders().getId() + ", " + myContent);
}
}
I am able to successfully exchange messages with this setup. I would expect, that the IDs of the sent message and the received message are equal. But they are always different UUIDs.
Is there a way that the message keeps the same ID all the way from the producer, through the RabbitMq, to the consumer?
Spring Messaging messages are immutable; they get a new ID each time they are mutated.
You can use a custom header or IntegrationMessageHeaderAccessor.CORRELATION_ID to convey a constant value; in most use cases, the correlation id header is set by the application to the ID header at the start of a message's journey.

Not able to to filter messages received using condition attribute in Spring Cloud Stream #StreamListener annotation

I am trying to create a event based system for communicating between services using Apache Kafka as Messaging system and Spring Cloud Stream Kafka.
I have written my Receiver class methods as below,
#StreamListener(target = Sink.INPUT, condition = "headers['eventType']=='EmployeeCreatedEvent'")
public void handleEmployeeCreatedEvent(#Payload String payload) {
logger.info("Received EmployeeCreatedEvent: " + payload);
}
This method is specifically to catch for messages or events related to EmployeeCreatedEvent.
#StreamListener(target = Sink.INPUT, condition = "headers['eventType']=='EmployeeTransferredEvent'")
public void handleEmployeeTransferredEvent(#Payload String payload) {
logger.info("Received EmployeeTransferredEvent: " + payload);
}
This method is specifically to catch for messages or events related to EmployeeTransferredEvent.
#StreamListener(target = Sink.INPUT)
public void handleDefaultEvent(#Payload String payload) {
logger.info("Received payload: " + payload);
}
This is the default method.
When I run the application, I am not able to see the methods annoated with condition attribute being called. I only see the handleDefaultEvent method being called.
I am sending a message to this Receiver Application from the Sending/Source App using the below CustomMessageSource class as below,
#Component
#EnableBinding(Source.class)
public class CustomMessageSource {
#Autowired
private Source source;
public void sendMessage(String payload,String eventType) {
Message<String> myMessage = MessageBuilder.withPayload(payload)
.setHeader("eventType", eventType)
.build();
source.output().send(myMessage);
}
}
I am calling the method from my controller in Source App as below,
customMessageSource.sendMessage("Hello","EmployeeCreatedEvent");
The customMessageSource instance is autowired as below,
#Autowired
CustomMessageSource customMessageSource;
Basicaly, I would like to filter messages received by the Sink/Receiver application and handle them accordingly.
For this I have used the #StreamListener annotation with condition attribute to simulate the behaviour of handling different events.
I am using Spring Cloud Stream Chelsea.SR2 version.
Can someone help me in resolving this issue.
It seems like the headers are not propagated. Make sure you include the custom headers in spring.cloud.stream.kafka.binder.headers http://docs.spring.io/autorepo/docs/spring-cloud-stream-docs/Chelsea.SR2/reference/htmlsingle/#_kafka_binder_properties .

Resources