Spring websocket STOMP Unsubscribe from eventHandler - java

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.
In case if subscription is invalid, for instance, current user can not see that subscription, I would like Broker (I use SimpleMessagingBroker) to "forget" that subscription, or preferably, do not register it at all.
My questions are:
Can I make Broker to not register the subscription, if I move handling of subscription request to incoming message interceptor and stop message propagation?
What else could be used from this event handler to cancel the subscription?

You need to create you ChannelInterceptor implementation. Just extend ChannelInterceptorAdapter and override preSend(Message<?> message, MessageChannel channel). Here you will get access to headers with session information for validation. Also you need to registrate your interceptor
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.configureBrokerChannel().interceptors(new YourInterceptor())
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
More information here How to reject topic subscription based on user rights with Spring-websocket

Related

Change websocket scope (from application to session/view)

I created a basic web socket with a tutorial.
Here is a configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}
And here is the message handling controller:
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
return new OutputMessage("Hello World!");
}
Everything works, but from my investigation, it looks like the WebSockets by default has an application scope (by connecting to the channel I can see all calls, from all users).
What I want to do is to be able to see only calls from the current user session or current view only.
Any ideas on how to apply these configurations?
I was able to solve this puzzle, so I'm sharing with you my findings.
First, I found information that a simple in-memory message broker can not handle this:
/*
* This enables a simple (in-memory) message broker for our application.
* The `/topic` designates that any destination prefixed with `/topic`
* will be routed back to the client.
* It's important to keep in mind, this will not work with more than one
* application instance, and it does not support all of the features a
* full message broker like RabbitMQ, ActiveMQ, etc... provide.
*/
But this was misleading, as it can be easily achieved by #SendToUser annotation.
Also, the important thing that now on the client-side, you need to add an additional prefix /user/ while subscribing to the channel, so the solution would be:
On the server-side: change #SendTo("/topic/messages") into #SendToUser("/topic/messages").
On the client-side: /topic/messages into the /user/topic/messages.

Does JMSTemplate producer open a thread for each message?

I'm building a REST API application with Spring Boot 2.1.6. I want to use JMS messaging in my app with Active MQ package (org.apache.activemq). I have MyEventController class which receives all kinds of events through http requests. I then want to send the information about the events as a JMS message to a topic so that the message consumer will update the database with the information from the events.
The reason I want to use JMS here is to not hold the Spring thread which handle http request and have the consumer open a separate thread to do potentially a lot of time consuming updates to the database. However I'm wondering if JMSTemplate stays always one thread. Because if a new thread is opened for each http request then the solution is not so scalable.
This is my code for producer:
#RestController
public class MyEventController {
#Autowired
private DBHandler db;
#Autowired
private JmsTemplate jmsTemplate;
#RequestMapping(method=GET, path=trackingEventPath)
public ResponseEntity<Object> handleTrackingEvent(
#RequestParam(name = Routes.pubId) String pubId,
#RequestParam(name = Routes.event) String event) {
jmsTemplate.convertAndSend("topic1", "info#example.com");
return new ResponseEntity<>(null, new HttpHeaders(), HttpStatus.OK);
}
consumer:
#Component
public class JSMListener {
#JmsListener(destination = "topic1", containerFactory = "topicListenerFactory")
public void receiveTopicMessage(String event) {
// do something...
}
}
JmsTemplate has no concept of background threads or async sending. It's a class design for simplifying usage of java.jms.Session and embedding it with usual Spring concepts e.g. declarative transaction management with #Transactional.
In your example convertAndSend() will execute as part of the request processing thread. The method will block until the JMS broker responds to the application that the message was added to the destination queue or throw an exception if there was a problem e.g. queue was full.

Let websocket clients subscribe, only if there's an endpoint for that channel

In my spring boot app, I have the following websocket controller:
#Controller
public class QuoteController {
#Autowired
private RabbitTemplate rabbitTemplate;
#SubscribeMapping("/quote/{symbol}")
public void singleQuote(#DestinationVariable("symbol") String symbol, Principal user) {
System.out.println("Salam");
}
}
I would like the clients to be able to subscribe to a channel, only if there exists an endpoint like above for that channel. Apparently (based on the logs in the browser) clients can subscribe to any arbitrary channel.
(and btw, isn't this a bad practice to let clients to subscribe to any channel? Specially security wise. Malicious clients can subscribe to millions of channel and cause the server to slow down, or subscribe to channels that are for internal use only)

Push Message from ActiveMQ to Spring Controller

I'm using Spring MVC, ActiveMQ and WebSocket(via sock.js and stomp.js) to build a real-time data delivery application.
As we know, when a producer(another desktop application) push a message to ActiveMQ, and the onMessage() method will catch it.
public class MessageReceiver implements MessageListener {
public void onMessage(Message message) {
System.out.println(message);
// How to push the message to a Controller in Spring?
}
}
Most of the tutorials just print the message to the console.
I have another controller called WebSocketController:
#Controller
public class WebSocketController {
#SubscribeMapping("/getRealTimeResult.action/{submissionId}")
public Message getRealTimeResultAction(
#DestinationVariable long submissionId) {
return new Message("Message content from onMessage() method");
}
}
I want to push the message received in onMessage() method to the getRealTimeResultAction() method. Can you tell me how to do it?
I know that the ActiveMQ can communicate with the browser using stomp via the port 61613.
I don't want to do this because I think the MQ should be transparent to the user. Also I need to do some authorization in the WebSocketController.
Generally speaking an #Controller with #SubscribeMapping and #MessageMapping methods can handle subscriptions and messages from STOMP clients connected over WebSocket.
From your description it's not clear what you're trying to do. Was the message pushed to ActiveMQ via STOMP from a browser client or was it produced by some other back-end JMS client? Also the MessageReceiver receives an actual message while the #Controller method has an #SubscribeMapping method for handling a subscription from a STOMP client. It's not clear what you're trying to do. Please provide more information so I can provide a better answer.

spring websockets: sending a message to an offline user - messages not enqueued in message broker

I have a webapp with spring and websockets using a message broker (activemq).
here is my config class:
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/topic","/queue/");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}
}
I have a scheduled task that constantly pushing messages to the username "StanTheMan" :
#Scheduled(fixedDelay = 1000)
public void sendGreetings() {
HelloMessage hello = new HelloMessage();
hello.setContent("timeStamp:" + System.currentTimeMillis());
String queueMapping = "/queue/greetings";
template.convertAndSendToUser(
"DannyD",
queueMapping,
hello);
}
Currently, if the user is NOT connected via a websocket to the server - all the messages for him are not being en-queued for him, they simply discarded. whenever he connects - fresh messages are being en-queued for him.
Is it possible for me to "convertAndSendToUser" a message to an offline user in any way? i would like to en-queue messages with an expired time for offline users to be later on pushed when they are connecting again and the expired time wasn't over.
how can i achieve that? Obviously using a real message broker (activemq) supposed to help achieving that, but how?
Thanks!
Indeed this feature can only be used to send messages to a (presently) connected user.
We plan to provide better ways to track failed messages (see SPR-10891). In the meantime as a workaround you could inject the UserSessionRegistry into your #Scheduled component and check if the getSessionIds methods returns a non-empty Set. That would indicate the user is connected.
One alternative may be to use your own convention for a queue name that each user can subscribe to (probably based on their user name or something else that's unique enough) in order to receive persistent messages. The ActiveMQ STOMP page has a section on persistent messages and expiration times.
This is a default behavior for message brokers.
And you haven't setup the ActiveMQ broker as your default broker so Spring is setting up a in-memory broker.
To achieve what you want setup/give your details of activeMQ to your spring websocket message broker. As Spring doesn't provide these settings, you have to do all the persistence settings at the ActiveMQ side. .
To setup ActiveMQ for spring websocket message broker you also need to enable stomp and use this:
registry.enableStompBrokerRelay("/topic").setRelayHost("hostName").setRelayPort("00000");
For more information checkout:
http://docs.spring.io/spring/docs/4.0.0.RELEASE/javadoc-api/org/springframework/messaging/simp/config/StompBrokerRelayRegistration.html

Categories