I am building an application using Spring Websockets on a clustered tomcat environment with a RabbitMQ broker. I have an API module which needs to register the endpoint to listen to. I followed the normal examples and came up with this config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(final MessageBrokerRegistry config)
{
config.enableStompBrokerRelay("/topic/")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry)
{
registry.addEndpoint("/updates")
.setAllowedOrigins("*")
.withSockJS();
}
}
While this works, it doesn't solve my issue as it appears the WebSocket and relay config are all bundled into the API module therefore leaving other layers unable to reuse the broker. I need the stomp message broker relay configuration to happen at the service layer so that other modules of our app can push messages to topics in RabbitMQ which then turn around and notify the API module to update all open websockets.
Below is a sample diagram of the relevant layers in our application and what I am trying to accomplish. I need to allow the module "Cron Message Sender" to push messages to everyone who is subscribed to a message topic through our other API modules.
So the second approach did in fact work. I configured the websockets to be run independently (no relay) and then I made a separate AMQP message broker connection at the service layer to allow communication between services. In the API module, I simply listened to the AMQP message broker and then manually forwarded those messages to the SimpMessagingTemplate which notified the websocket subscribers. I am not sure if this is technically the "right" way to do it but it seems to be working great and I do not yet see any issues with the implementation. In fact, I actually think I may prefer this approach as I now just gave all my services the ability to talk to each other with more types of messages than what I originally needed for the websockets.
Here is the new configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(final MessageBrokerRegistry config)
{
config.enableSimpleBroker("/topic");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry)
{
registry.addEndpoint("/updates")
.setAllowedOrigins("*")
.withSockJS();
}
}
And here is where I listen to the message broker and forward the messages to the websocket subscribers:
#Component
public class SendWebSocketUpdates
{
private static final Logger logger = LoggerFactory.getLogger(SendWebSocketUpdates.class);
private final Gson gson;
#Autowired
private SimpMessagingTemplate messagingTemplate;
#Autowired
private MessageBrokerConsumer<String> messageBrokerConsumer;
public SendWebSocketUpdates()
{
this.gson = new Gson();
}
#PostConstruct
public void init()
{
//listen for incoming AMQP messages from the rabbitmq server and forward them to the websocket subscribers
messageBrokerConsumer.addListener((message, topicName) -> {
final String destination = "/topic/" + topicName;
final String messageJson = gson.toJson(message.getBody());
//check to see if trace logging is enabled
if (logger.isTraceEnabled())
{
logger.trace("Sending Message to \"{}\": {}", destination, messageJson);
}
//broadcast the via a STOMP message to subscribers of this topic
messagingTemplate.convertAndSend(destination, messageJson);
});
}
}
It's easy to solve this problem. I waste a whole day to find the solution.
Here 's my answer for the same problem.
The key is setUserDestinationBroadcast and setUserRegistryBroadcast:
registry.enableStompBrokerRelay("/topic/", "/queue/", "/exchange/")
.setUserDestinationBroadcast("/topic/log-unresolved-user")
.setUserRegistryBroadcast("/topic/log-user-registry")
Related
I have one microservice in which I have one rest controller and one rabbitMQ receiver, sometimes when I start the server receiver and API both are working fine but after some time receiver is not working it stops receiving new messages from the queue.
This is my rabbit configuration bean class:
#Configuration
public class RabbitMQConfiguration {
#Bean
public Queue claimQueue() {
return new Queue("queue");
}
}
other credentials-related configurations are in the application.properties file. And this is my receiver part
#RabbitListener(queues = "queue")
public class ClaimValidatorReciever {
#RabbitHandler
public void reciever(RabbitMQValidatorModel validatorModel)
throws InterruptedException, ExecutionException { }
Is it possible to have both in one microservice, and if possible any idea about this issue?
I tried to restart my server when this thing happen but after restarting this is working but after some time again facing this same issue of not fetching data from queue
Is it possible to assign different Controller or at least a different MessageMapping for each of the stomp endpoints?
My goal is to have client1 connecting to /endpoint1 and client2 connecting to /endpoint2 without client1 being able to access any topics/queues of /endpoint2 and vice-versa (they are completely different applications).
So they would be completely encapsulated implementations based on the endpoint to which they connect.
Bonus points for being able to use different Jackson ObjectMapper for each endpoint as well.
So far I have created a websocket configuration with 2 endpoints (/endpoint1 and /endpoint2):
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/endpoint1", "/endpoint2")
.setAllowedOriginPatterns("*")
.withSockJS();
}
// etc...
}
I also have a Controller which can process requests and send them to appropriate user response queue, but it's accessible from both endpoints:
#Controller
public class WebSocketController {
#MessageMapping("/request")
#SendToUser("/queue/response")
public MyResponse handleMessage(MyRequest request) {
// implementation
}
}
Current behaviour:
It doesn't matter which endpoint is my client connecting to in my current implementation, both can access the same topics, which is unwanted behavior.
You should alter your application design so that clients would be only able to send messages to their respective STOMP destinations. You can name your STOMP destinations in a client-specific prefixed way such as:
/endpoint1/request
/endpoint2/request
You should then be able to define different #MessageMapping-annotated message handlers after the above naming pattern:
#Controller
public class WebSocketController {
#MessageMapping("/endpoint1/request")
#SendToUser("/endpoint1/queue/response")
public MyResponse handleClient1Message(MyRequest request) {
// process STOMP message from client 1
}
#MessageMapping("/endpoint2/request")
#SendToUser("/endpoint2/queue/response")
public MyResponse handleClient2Message(MyRequest request) {
// process STOMP message from client 2
}
}
I'm trying to understand how to publish/broadcast messages using websockets with Spring Boot to a Javascript application. All examples I can find are making use of a StompJs client - I however am unable to use StompJs in my client code, and I'm not sure my backend is correct which doesn't help.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/subscribe")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
Just using a simple #Scheduled to produce the time every 5 seconds, and send it to the time topic (Well, I believe that's what it's doing...)
#Component
#Slf4j
public class TimeSender {
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
private SimpMessagingTemplate broker;
#Autowired
public TimeSender(final SimpMessagingTemplate broker) {
this.broker = broker;
}
#Scheduled(fixedRate = 5000)
public void run() {
String time = LocalTime.now().format(TIME_FORMAT);
log.info("Time broadcast: {}", time);
broker.convertAndSend("/topic/time", "Current time is " + time);
}
}
There are a few points I'm a little confused about when trying to test this. Using the Simple websocket client plugin for Chrome, I have to add websocket to the end of my request in order to connect. A connection would like ws://localhost:8080/subscribe/websocket Without the websocket I can't connect, but I can't find this mentioned in any examples or Spring documentation?
The second question is how do I subscribe to the time topic? All StompJs clients call something like client.subscribe("time") etc.
I've tried ws://localhost:8080/subscribe/topic/time/websocket but no luck in receiving any timestamps.
I'm not sure if my backend code is just wrong, my URL is wrong, or I'm just missing something else.
Note: My #Controller is missing from above as I'm just focused on pushing messages from Spring to clients at this stage, not receiving messages and It's my understanding controllers just deal with incoming?
Well, I suppose if one searches obsessively enough the answer eventually turns up. Almost immediately after finding your post I found the answer I needed at http://www.marcelustrojahn.com/2016/08/spring-boot-websocket-example/. There is a really good example that essentially does what you are describing. The difference is they are using a Spring SimpMessagingTemplate to send messages to the queue. Once I followed his pattern, it all worked like a charm. Here is the relevant code snippet:
#Autowired
SimpMessagingTemplate template
#Scheduled(fixedDelay = 20000L)
#SendTo("/topic/pingpong")
public void sendPong() {
template.convertAndSend("/topic/pingpong", "pong (periodic)")
}
The method is void so the convertAndSend() method handles publishing to the topic, not the return statement as just about every other tutorial I've seen on the web indicates. This helped solve my problem.
I have implemented the following websocket endpoint
#MessageMapping("/socket/{myId}/")
#SendTo("/queue/myqueue")
public MyObject getObject(#DestinationVariable String myId) throws Exception {
return new MyObject("MyId:" + myId);
}
Now how can I send message to that endpoint from one of my service.java class?
There will be front-end client as well, which will read the message from websocket once the service.java class's method send some message to websocket endpoint. I am a little confused that how can I do that?
Any help would be appreciated
When using a raw websocket(without STOMP), the message sent lacks of information to make Spring route it to a specific message handler method (we don't have any messaging protocol), so instead of annotating your controller, you'll have to implement a WebSocketHandler by extending TextWebSocketHandler
public void handleTextMessage(WebSocketSession session, TextMessage message){
}
Checkout an example here spring boot websocket without STOMP and SockJs
You should take a look at SimpMessagingTemplate.
For example, if you want to send a message for a specific user from your service class:
#Autowired
private SimpMessagingTemplate messagingTemplate;
public void sendMessage(User user, String message) {
Objects.requireNonNull(user);
Objects.requireNonNull(message);
messagingTemplate.convertAndSendToUser(user.getUsername(), "/queue/myqueue", message);
}
I'm trying to use RabbitMq with spring WebSocketMessageBroker, across distributed microservices.
The setup I'm working with is
Within the WebSocketMessageBroker, I'm using the following config, taken from the docs:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/push").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/", "/topic/", "/app");
registry.setApplicationDestinationPrefixes("/app");
registry.setPathMatcher(new AntPathMatcher("."));
}
}
Given this config, what exchange / queue should MyMicroservice publish to in order for the message to be proxied through to the Stomp service?
I've tried the following (on the publishing side -- within MyMicroservice)
#Configuration
#SpringBootApplication
#EnableRabbit
public class Config {
public static final String WEB_QUEUE = "/topic/myNotificationTopic";
public static final String WEB_EXCHANGE = "web.exchange";
#Bean
Queue webQueue() {
return new Queue(WEB_QUEUE, false);
}
#Bean
TopicExchange webExchange() {
return new TopicExchange(WEB_EXCHANGE);
}
#Bean
Binding binding(Queue webQueue, TopicExchange webExchange) {
return BindingBuilder.bind(webQueue).to(webExchange).with(WEB_QUEUE);
}
}
#Component
public class ExamplePublisher {
#Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage() {
amqpTemplate.convertAndSend(Config.WEB_QUEUE, "Hello, world");
}
}
However, the message doesn't appear to be proxied over the WebSocket connection.
To be clear, my questions are:
Is this type of distributed configuration supported out-of-the-box?
Given the example config, what is the relationship between RabbitMq topics that the services can publish to, and the Stomp messagebroker proxying?
It isn't clear by your explanation why you are going to use Spring AMQP for the STOMP interaction, although it is possible anyway.
I'd suggest to take a look to the StompClient support in the Spring Messaging, if you are going to send STOMP messages to the target destination directly from Java.
With the Spring AMQP (or just AMQP protocol) you should follow some RabbitMQ STOMP Adapter rules:
Topic Destinations
For simple topic destinations which deliver a copy of each message to all active subscribers, destinations of the form /topic/<name> can be used. Topic destinations support all the routing patterns of AMQP topic exchanges.
Messages sent to a topic destination that has no active subscribers are simply discarded.
AMQP 0-9-1 Semantics
For SEND frames, the message is sent to the amq.topic exchange with the routing key <name>.
For SUBSCRIBE frames, an autodeleted, non-durable queue is created and bound to the amq.topic exchange with routing key <name>. A subscription is created against the queue.
Pay attention that you should have subscription first anyway, otherwise your messages will be lost without subscribers.