Custom spring websocket security with rabbitmq - spring-boot

I'm working with websocket to push notification using rabbitmq like a message broker. In controller i using SimpMessageTemplate to convertAndSendToUser a message via the nick name of user
#PostMapping("/{nickName}/notification")
public void send(#PathVariable String nickName,
#RequestBody NotificationRes notificationRes) {
messagingTemplate.convertAndSendToUser(nickName, "/exchange/amq.direct/notification", notificationRes);
}
I make some configuration by using JWT to authorize user and set user to StompHeaderAccessor when user start connecting.
Spring security messaging currently convert send to user. With that endpoint spring will convert it to /exchange/amq.direct/notification-user{session_id}. And rabbitmq will create an auto deleted queue. So, if I have 100 connect in the same time. It will create 100 queue. I want to custom this flow to create one queue and spring will using message header for spring can know the user to send message to.
I did some research about UserDestinationResolver but I still don't know how to make spring can handle with header instead of url like ...-user{session_id}. #.#. What should i do for this situatioin?

Related

Spring Cloud Bus Getting Refresh Event for Plain Text

I am trying to get our config server to host plain text files more dynamically. I am currently having a Spring config server and a few services using it to get configuration. I am also have kafka up for Spring Cloud Bus connected to both config server and the clients.
Additionally, I am using config server to server some plain text files (as described in https://cloud.spring.io/spring-cloud-static/spring-cloud.html#_serving_plain_text). I am wishing to get the client to refresh, if the plain text files are updated.
When normal properties are updated, my client through spring cloud bus can receive the refresh request, and perform the refresh, however, when the plain text files are updated, the same thing doesn't happen. The config server logs showed that it registered the changes, but there's no refresh event broadcast to the clients through kafka.
I am wondering if there's a way to achieve this automatic refresh for plain text files.
My current thought:
Have a refresh event listener to catch the kafka message, and do refresh for necessary bean.
example:
#Configuration
public class FileReloadListener implements ApplicationListener<RefreshRemoteApplicationEvent> {
#Override
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
// refresh the needed beans
}
}
Any idea? Thanks!

How to get number of subscribers in spring boot for redis

I am new to redis and i am using spring data redis for implementing some task queue feautre. Since i wanna do some failover checking, is there any a way to get the number of subscriber like in command "pubsub numsub " for a specific channel. Many thanks
if you use spring reactive-Redis with spring boot you can create a specific topic and get the all subscribers that received the published event as a return by publishing a vent ny using convertAndSend method of ReactiveRedisTemplate. but the spring doesn't provide that in imperative way. the convertAndSend method of RedisTemplate doesn't return anything. the return type is void.
stringStringReactiveRedisTemplate
.convertAndSend(
"your topic",
"event message"
).subscribe(receivedCount -> {
System.out.println("num of subscribers the event received: " + receivedCount);
});
you can another way by using the redis connection. it doesn't matter if you use with spring boot. you can get the connection from the redisTemplate and call the publish method to publish the event like below. then you have to provide your channel name and the message by byte[].
Long receivedCount = redisTemplate.getConnectionFactory().getConnection().publish(
"your channel".getBytes(),
"message".getBytes()
);
now you will receive how many live subscribers that event received. (only for that mentioned topic name).
read more https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:pubsub:publish

Spring boot SSE (Server sent events): how to send response dynamically

I am new to SSE (Server Sent Events) . I found a way to send response using SSE in spring boot. I am able to send response for every 20 seconds. I used below code for the same.
public Flux<UserDto> fetchUserEvent() {
return Flux.interval(Duration.ofSeconds(20)).map(intr -> generateUserEvent()).flatMapIterable(x -> x);
}
generateUserEvent() - verify if new user added in DB. If it found user data, will return the same or will return empty object (new UserDto())
But the problem is , this method being called for every 20 seconds .
But , My requirement is to send the empty response to client every 20 seconds and send the respone whenever new user added to DB.
How can I achieve my goal? Or I am totally wrong conceptually.
You should create an event publisher and listener so you can send an event to the emitter when a new user is registered.
Since you're using spring-boot and probably hibernate you can see example here on how to intercept hibernate events.
Hibernate interceptor or listener with Spring Boot and Spring Data JPA
I would personally not use flux and make a scheduled method in spring to send those empty ping responses to the emitter. example of this can be found here: https://www.roytuts.com/server-sent-events-spring-push-notifications/
More info for spring events in general can be found here:
https://www.baeldung.com/spring-events

Webflux & WebSocket, send to specific session id

Every session data passed into the socket is broadcasted to all users since every session subscribes to the UnicastProcessor eventPublisher.
How can I send by event data to a single session id and not to all of them?
#Override
public Mono<Void> handle(WebSocketSession session) {
WebSocketMessageSubscriber subscriber = new WebSocketMessageSubscriber(eventPublisher);
session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map(this::toEvent)
.subscribe(subscriber::onNext, subscriber::onError, subscriber::onComplete);
return session.send(outputEvents.map(session::textMessage));
}
My use-case requires me to include both options for broadcasting any changed state with any client to all sockets connected plus the abillity to send response to a specific client (sessionId) that send a request within a specific event
Github link
or should It be routed to 2 different handlers from the same websocket path?
note that from javascript
new WebSocket(url/path) creates a socket connection
there is no way to change the path without creating or instantiating a new WebSocket object which is not wanted.
I'm not interested in creating for every browser client 2 sockets...
so my goal is to base the server connection via 1 single websocket path
#Bean
public HandlerMapping webSocketMapping(UnicastProcessor<Event> eventPublisher, Flux<Event> events) {
Map<String, Object> map = new HashMap<>();
map.put("/websocket/chat", new ChatSocketHandler(eventPublisher, events));
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
simpleUrlHandlerMapping.setUrlMap(map);
//Without the order things break :-/
simpleUrlHandlerMapping.setOrder(10);
return simpleUrlHandlerMapping;
}
if so would be glad to see an example of such solution
With servlet based web socket, it's possible because you can connect websocket to messaging brokers. Then the messaging broker will take care of sending messages to specific client.
But with webflux based websocket that spring is provided , I couldn't manage to do bring messaging brokers into action. It seems that there is no support yet for it in spring webflux.
Find a sample with servlet stack here:
https://github.com/bmd007/RealtimeNoteSharing.git

Spring WebSockets ActiveMQ convertAndSendToUser

I have a Spring Boot app (Jhipster) that uses STOMP over WebSockets to communicate information from the server to users.
I recently added an ActiveMQ server to handle scaling the app horizontally, with an Amazon auto-scaling group / load-balancer.
I make use the convertAndSendToUser() method, which works on single instances of the app to locate the authenticated users' "individual queue" so only they receive the message.
However, when I launch the app behind the load balancer, I am finding that messages are only being sent to the user if the event is generated on the server that their websocket-proxy connection (to the broker) is established on?
How do I ensure the message goes through ActiveMQ to whichever instance of the app that the user is actually "connected too" regardless of which instance receives, say an HTTP Request that executes the convertAndSendToUser() event?
For reference here is my StompBrokerRelayMessageHandler:
#Bean
public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() {
StompBrokerRelayMessageHandler handler = (StompBrokerRelayMessageHandler) super.stompBrokerRelayMessageHandler();
handler.setTcpClient(new Reactor2TcpClient<>(
new StompTcpFactory(orgProperties.getAws().getAmazonMq().getStompRelayHost(),
orgProperties.getAws().getAmazonMq().getStompRelayPort(), orgProperties.getAws().getAmazonMq
().getSsl())
));
return handler;
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/queue", "/topic")
.setSystemLogin(orgProperties.getAws().getAmazonMq().getStompRelayHostUser())
.setSystemPasscode(orgProperties.getAws().getAmazonMq().getStompRelayHostPass())
.setClientLogin(orgProperties.getAws().getAmazonMq().getStompRelayHostUser())
.setClientPasscode(orgProperties.getAws().getAmazonMq().getStompRelayHostPass());
config.setApplicationDestinationPrefixes("/app");
}
I have found the name corresponding to the queue that is generated on ActiveMQ by examining the headers in the SessionSubscribeEvent, that is generated in the listener when a user subscribes to a user-queue, as simpSessionId.
#Override
#EventListener({SessionSubscribeEvent.class})
public void onSessionSubscribeEvent(SessionSubscribeEvent event) {
log.debug("Session Subscribe Event:" +
"{}", event.getMessage().getHeaders().toString());
}
Corresponding queues' can be found in ActiveMQ, in the format: {simpDestination}-user{simpSessionId}
Could I save the sessionId in a key-value pair and just push messages onto that topic channel?
I also found some possibilities of setting ActiveMQ specific STOMP properties in the CONNECT/SUBSCRIBE frame to create durable subscribers if I set these properties will Spring than understand the routing?
client-id & subcriptionName
Modifying the MessageBrokerReigstry config resolved the issue:
config.enableStompBrokerRelay("/queue", "/topic")
.setUserDestinationBroadcast("/topic/registry.broadcast")
Based on this paragraph in the documentation section 4.4.13:
In a multi-application server scenario a user destination may remain
unresolved because the user is connected to a different server. In
such cases you can configure a destination to broadcast unresolved
messages to so that other servers have a chance to try. This can be
done through the userDestinationBroadcast property of the
MessageBrokerRegistry in Java config and the
user-destination-broadcast attribute of the message-broker element in
XML
I did not see any documentation on "why" /topic/registry.broadcast was the correct "topic" destination, but I am finding various iterations of it:
websocket sessions sample doesn't cluster.. spring-session-1.2.2
What is MultiServerUserRegistry in spring websocket?
Spring websocket - sendToUser from a cluster does not work from backup server

Resources