Hazelcast client disconnect from whole cluster - cluster-computing

Is there a way for a cluster member to receive a callback when a client disconnects from the cluster (not just a single member).
On the client end the disconnect lifecycle event is only called when the client COMPLETELY disconnects from the cluster, but on the cluster members the 'owner' member appears to get disconnect/connect lifecycle events when there is a socket disconnect/error, so we cannot use the disconnect as an indication of cluster disconnect (it's only temporarily disconnected from this owner member).
We want to use the client disconnect (from the whole cluster) as the trigger to cleanup shared state, in particular, remove entries from an IMap keyed by the client UUID

You can add your listener to the client service.
var hazelcast = Hazelcast.newHazelcastInstance();
hazelcast.getClientService().addClientListener(new ClientListener() {
#Override
public void clientConnected(Client client) {
// TODO
}
#Override
public void clientDisconnected(Client client) {
// TODO
}
});

Related

How to make my Java SpringBoot application to proxy MQTT Broker WebSocket 8083 port?

Let's suppose we have an Spring Boot application called A. And we have a MQTT Broker that supports MQTT WebSocket Service.
The device used to connect directly to the MQTT Broker's 8083 port to get MQTT WebSocket Service. But now I want the device to connect to the A's 80 port to get the same MQTT WebSocket Service. For example, the device connect to the ws://A_HOST:80/mqtt, it should work the same as it connect to ws://MQTT_BROKER_HOST:8083/mqtt.
My solution is to use the org.springframework.web.reactive.socket.WebSocketHandler in A. When device connect to A, I would use org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient in A to connect to MQTT Broker at the same time. Whatever data the device sends to A, the A should send the same data to the MQTT Broker. And Whatever the data MQTT Broker responses(or sends), the A should send the same data to device. I think the solution can implement my needs.
I am using WebFlux.
I write the code below, but unfortunately it doesn't work:
public class ReactiveWebSocketHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession serverSession) {
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute(
URI.create("ws://xxx.0.0.1:8083/mqtt"), // xxx.0.0.1:8083 is the MQTT Broker Address
new WebSocketHandler() {
#Override
public List<String> getSubProtocols() {
return Collections.singletonList("mqtt");
}
#Override
public Mono<Void> handle(WebSocketSession clientSession) {
return clientSession.send(serverSession.receive()) // Whatever data the device sends to A, the A should send the same data to the MQTT Broker
.and(
serverSession.send(clientSession.receive())); // Whatever the data MQTT Broker responses(or sends), the A should send the same data to device
}
});
return Mono.empty(); // Since I have written the logic above, I don't know what should I write here
}
#Override
public List<String> getSubProtocols() {
return Collections.singletonList("mqtt");
}
}
Could you tell me how to write the code that can implements such logic? Thanks!

How can I manage sessions with stomp protocol?

I am developing websocket in spring boot, react environment.
I am using stomp protocol and I am storing the session in concurrentHashMap().
This is because if there is no subscribing session at the subscription point, data transmission stops, and when there is more than one, data must be periodically transmitted (published).
public final static Map<String, String> subscribeSessions = new ConcurrentHashMap<>();
#Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
String sessionId = accessor.getSessionId();
switch (accessor.getCommand()) {
case CONNECT:
break;
case DISCONNECT:
break;
case SUBSCRIBE:
if(!accessor.getDestination().contains("/user/queue")) {
subscribeSessions.put(accessor.getSessionId(),
accessor.getDestination());
}
break;
case UNSUBSCRIBE:
subscribeSessions.remove(accessor.getSessionId());
default:
break;
}
}
I tried to store it in hashMap as below in that one user can subscribe to multiple subscription points as shown below.
Session A - /Topic/ex
Session A - /topic/ex/2
However, this is not possible because hashmap does not allow duplicate keys.
Is there any way to solve this?
Or is there a way to check how many people are subscribing to subscription points?

How to limit the number of stomp clients in Spring, subscribing to a specific topic, based on a condition?

I have been researching for a way to limit the number of clients who can subscribe to a specific stomp topic but have not yet understood, which could be the right approach according to my needs.
My use case is a game, which I am developing in Angular (ng2-stompjs stomp client) and Spring Boot Websockets (for the moment, the Spring in-memory message broker is in use).
The idea is that a user can be connected and subscribed to a "/lobby" stomp topic, and there he sees the opened game rooms, that could be in different statuses. for example, in-play or not started yet due to the low number of players joined.
I'd like to intercept and programmatically restrict a possible subscription of a client, to a specific "/room/{roomId}" topic, IF the MAX number of players has been reached, for example, 4. There could also be some simple client-side validation to restrict that, but I believe only client-side is not sufficient
So my main questions are:
How can a specific stomp topic subscription be intercepted in Spring?
Is it possible to return to the client-requestor some kind of error message that subscription could not be done?
I'd really appreciate your help, thank you in advance!
You could implement a StompEventListener which listens for subscriptions, in this we can have map mapping a destination(room number) versus the count of number of players in that particular room. if the count is already at max reject the subscription.
#Service
class StompEventListener() {
private Map<String, int> roomIdVsPlayerCount = new HashMap<>();
#EventListener
public void handleSubscription(SessionSubscribe event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
String destination = accessor.getDestination();
String roomId = destination.substring(...); //Parsed RoomID
if(roomIdVsPlayerCount.get(roomId) == MAX_ALLOWED_PLAYERS) {
//Throw exception which will terminate that client connection
or, send an error message like so:
simpMessagingTemplate.convertAndSend(<some_error_message>);
return;
}
//So it is not at maximum do further logic to actually subscribe
user and
roomIdVsPlayerCount.get(roomId) += 1;
}
#EventListener
public void handleUnsubscription(SessionUnsubscribe event) {
...
}
}
Useful References:
SessionSubscribeEvent (For handling the subscriptions)
ConvertAndSend. (For sending the error messages to client.)
EDIT
Please try sending the exception from a channel Interceptor since the above did not send the exception , so that it gets propagated to the client. The map we defined earlier can be defined as a bean in a separate class accessible(with #Autowired) to both event handler(for incrementing and decrementing) and TopicSubscriptionInterceptor(for validation).
#Component
class TopicSubscriptionInterceptor implements ChannelInterceptor {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel){
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
String destination = accessor.getDestination();
String roomId = destination.substring(...); //Parsed RoomID
if(roomIdVsPlayerCount.get(roomId) == MAX_ALLOWED_PLAYERS) {
//Throw exception which will terminate that client connection
}
//Since it is not at limit continue
}
}
Useful reference for implementing a TopicSubscriptionInterceptor: TopicSubscriptionInterceptor

SpringJMS - How to Disconnect a MessageListenerContainer

I want to disconnect the DefaultMessageListenerContainer for a queue. I am using dmlc.stop(), dmlc.shutdown(). At the time of conneciton 5 consumer threads get connected to queue. when I try to disconnect, 4 of the consumers get disconnected, but 1 consumer remains connected. (See screenshot at the end of thread).
Environment
1. ActiveMQ with AMQP
2. SpringJMS with ApacheQpid
Problem
After calling destroy and stop method, there's still one consumer connected to the queue.
Required Solution
I want to know, how to cleanly disconnect a MessageListenerContainer with zero consumer connections to the queue.
Configurations and Code
#Bean
public DefaultMessageListenerContainer getMessageContainer(ConnectionFactory amqpConnectionFactory, QpidConsumer messageConsumer){
DefaultMessageListenerContainer listenerContainer = new DefaultMessageListenerContainer();
listenerContainer.setConcurrency("5-20");
listenerContainer.setRecoveryInterval(jmsRecInterval);
listenerContainer.setConnectionFactory(new CachingConnectionFactory(amqpConnectionFactory));
listenerContainer.setMessageListener(messageConsumer);
listenerContainer.setDestinationName(destinationName);
return listenerContainer;
}
private void stopListenerIfRunning() {
DefaultMessageListenerContainer dmlc = (DefaultMessageListenerContainer) ctx.getBean("messageContainer");
if (null != dmlc) {
if(!dmlc.isRunning()){return;}
dmlc.stop(new Runnable() {
#Override
public void run() {
logger.debug("Closed Listener Container for Connection {}", sub.getQueueName());
if (sub.getSubscriptionStatus() == SubscriptionStatus.DELETED
|| sub.getSubscriptionStatus() == SubscriptionStatus.SUSPENDED_DELETE) {
listenerHandles.remove(sub.getQueueName());
}
}
});
dmlc.destroy();
dmlc.shutdown();
}
}
}
listenerContainer.setConnectionFactory(newCachingConnectionFactory(amqpConnectionFactory));
You need to destroy the CachingConnectionFactory.
You generally don't need a caching factory with the listener container since the sessions are long-lived; you definitely should not if you have variable concurrency; from the javadocs...
* <p><b>Note: Don't use Spring's {#link org.springframework.jms.connection.CachingConnectionFactory}
* in combination with dynamic scaling.</b> Ideally, don't use it with a message
* listener container at all, since it is generally preferable to let the
* listener container itself handle appropriate caching within its lifecycle.
* Also, stopping and restarting a listener container will only work with an
* independent, locally cached Connection - not with an externally cached one.
if you want the connection cached, use a SingleConnectionFactory or call setCacheConsumers(false) on the CCF.

Stomp over websocket using Spring and sockJS message lost

On the client side javascript I have
stomp.subscribe("/topic/path", function (message) {
console.info("message received");
});
And on the server side
public class Controller {
private final MessageSendingOperations<String> messagingTemplate;
ï¼ Autowired
public Controller(MessageSendingOperations<String> messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#SubscribeMapping("/topic/path")
public void subscribe() {
LOGGER.info("before send");
messagingTemplate.convertAndSend(/topic/path, "msg");
}
}
From this setup, I am occasionally (around once in 30 page refreshes) experiencing message dropping, which means I can see neither "message received" msg on the client side nor the websocket traffic from Chrome debugging tool.
"before send" is always logged on the server side.
This looks like that the MessageSendingOperations is not ready when I call it in the subscribe() method. (if I put Thread.sleep(50); before calling messagingTemplate.convertAndSend the problem would disappear (or much less likely to be reproduced))
I wonder if anyone experienced the same before and if there is an event that can tell me MessageSendingOperations is ready or not.
The issue you are facing is laying in the nature of clientInboundChannel which is ExecutorSubscribableChannel by default.
It has 3 subscribers:
0 = {SimpleBrokerMessageHandler#5276} "SimpleBroker[DefaultSubscriptionRegistry[cache[0 destination(s)], registry[0 sessions]]]"
1 = {UserDestinationMessageHandler#5277} "UserDestinationMessageHandler[DefaultUserDestinationResolver[prefix=/user/]]"
2 = {SimpAnnotationMethodMessageHandler#5278} "SimpAnnotationMethodMessageHandler[prefixes=[/app/]]"
which are invoked within taskExecutor, hence asynchronously.
The first one here (SimpleBrokerMessageHandler (or StompBrokerRelayMessageHandler) if you use broker-relay) is responsible to register subscription for the topic.
Your messagingTemplate.convertAndSend(/topic/path, "msg") operation may be performed before the subscription registration for that WebSocket session, because they are performed in the separate threads. Hence the Broker handler doesn't know you to send the message to the session.
The #SubscribeMapping can be configured on method with return, where the result of this method will be sent as a reply to that subscription function on the client.
HTH
Here is my solution. It is along the same lines. Added a ExecutorChannelInterceptor and published a custom SubscriptionSubscribedEvent. The key is to publish the event after the message has been handled by AbstractBrokerMessageHandler which means the subscription has been registered with the broker.
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ExecutorChannelInterceptorAdapter() {
#Override
public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
if (accessor.getMessageType() == SimpMessageType.SUBSCRIBE && handler instanceof AbstractBrokerMessageHandler) {
/*
* Publish a new session subscribed event AFTER the client
* has been subscribed to the broker. Before spring was
* publishing the event after receiving the message but not
* necessarily after the subscription occurred. There was a
* race condition because the subscription was being done on
* a separate thread.
*/
applicationEventPublisher.publishEvent(new SessionSubscribedEvent(this, message));
}
}
});
}
A little late but I thought I'd add my solution. I was having the same problem with the subscription not being registered before I was sending data through the messaging template. This issue happened rarely and unpredictable because of the race with the DefaultSubscriptionRegistry.
Unfortunately, I could not just use the return method of the #SubscriptionMapping because we were using a custom object mapper that changed dynamically based on the type of user (attribute filtering essentially).
I searched through the Spring code and found SubscriptionMethodReturnValueHandler was responsible for sending the return value of subscription mappings and had a different messagingTemplate than the autowired SimpMessagingTemplate of my async controller!!
So the solution was autowiring MessageChannel clientOutboundChannel into my async controller and using that to create a SimpMessagingTemplate. (You can't directly wire it in because you'll just get the template going to the broker).
In subscription methods, I then used the direct template while in other methods I used the template that went to the broker.

Resources