Spring user destinations - java

I have a spring boot application that will have publish to user defined destination channels as such:
#Autowired
private SimpMessagingTemplate template;
public void send() {
//..
String uniqueId = "123";
this.template.convertAndSendToUser(uniqueId, "/event", "Hello");
}
Then a stomp over SockJS client can subscribe to it and receive the message. Suppose I have a stomp endpoint registered in my spring application called "/data"
var ws = new SockJS("/data");
var client = Stomp.over(ws);
var connect_fallback = function() {
client.subscribe("/user/123/event", sub_callback);
};
var sub_callback = function(msg) {
alert(msg);
};
client.connect('','', connect_callback);
Actually there will be more than one user client subscribing to the same distinct user destination, so each publish/subscribe channel is not one to one and I am only doing it this way since spring's concept of "/topic" have to be defined programmatically and "/queues" can only be consumed by one user. How do I know when a user destination no longer has any subscribers? And how do I delete a user destination?

#SendToUser('/queue/dest')
public String send() {
//..
String uniqueId = "123";
return "hello";
}
On the Client you would subscribe to '/user/queue/dest'
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-user-destination

After adding a channel interceptor and setting breakpoints within a code segment running in debug mode in eclipse, I found that there is a collection of websocket session and destination mappings in the registry objects that are held by the message handlers, which in turn can be seen stored inside the message channel object. I found this for the topics though not sure about purely user destination method.
However, Spring does not leave the api open for me to just call a function to get a list of all subscribers to every topic, at least not without passing in the message. Everything else that would have been helpful is set private so cannot be programmatically accessed. This is not helpful as i would like to trigger an action upon unsubscribe or disconnect of a client ONLY when the topic that is being unsubscribed/disconnected from does not have other listening clients left.
Now looking at embedding full featured broker such as ActiveMQ to see if it can potentially solve my problem

Related

Spring cloud stream: dynamic output channel strange behavior

I am using spring cloud stream version 2.1.0.RELEASE to send messages (in this case to Kafka) to channels dynamically defined based on the input received. The issue is that only every other message ends up in the correct channel, the other half end up in the default channel.
I used this sample as a starting point.
I am placing the channel I want to send to into a specific message header, then using a HeaderValueRouter to check that same header value to see which channel to output to.
I am configuring my application as follows:
#EnableBinding(CloudStreamConfig.DynamicSource.class)
public class CloudStreamConfig {
#Autowired
private BinderAwareChannelResolver resolver;
public static final String CHANNEL_HEADER = "channelHeader";
public static final String OUTPUT_CHANNEL = "outputChannel";
private final String defaultChannel = "defaultChannel";
#ServiceActivator(inputChannel = OUTPUT_CHANNEL)
#Bean
public HeaderValueRouter router() {
HeaderValueRouter router = new HeaderValueRouter(CHANNEL_HEADER);
router.setDefaultOutputChannelName(defaultChannel);
router.setChannelResolver(resolver);
return router;
}
public interface DynamicSource {
#Output(OUTPUT_CHANNEL)
MessageChannel output();
}
}
And in my controller I take in an object as well as a parameter defining what channel to send it to, then send it to the MessageChannel. The relevant code is below:
#Autowired
#Qualifier(CloudStreamConfig.OUTPUT_CHANNEL)
public MessageChannel localChannel;
...
#GetMapping(path = "/error/{channel}")
#ResponseStatus(HttpStatus.OK)
public void error(#PathVariable String channel) {
// build my object
Message message = MessageBuilder.createMessage(myObject,
new MessageHeaders(Collections.singletonMap(CloudStreamConfig.CHANNEL_HEADER, channel)));
localChannel.send(message);
}
If I send 10 messages to /error/someChannel I would expect to see 10 messages in someChannel. However, I see half of the messages in someChannel and the other half in defaultChannel. I have put a debugging counter variable in my messages and it sends the first message to the correct channel, and then every second message to the correct channel, while the others all go to the default channel.
What would be causing this and how can I fix it? Am I misusing my DynamicSource class? I assumed it would be tied to any autowired MessageChannel of the same name (and it does appear to be) but I'm wondering if there's something I'm missing. Or is there an unintended interaction with the BinderAwareChannelResolver? (I honestly don't know what this does, I only included it because the samples do)
There are two subscribers on the output channel - the channel binding (in the binder) and your router.
For DirectChannels, the default dispatching algorithm is round robin so you are sending messages alternately to the router and directly to the binder.
You need a different DirectChannel #Bean for the service activator so all messages go there, and thence to the binder after routing.
See sourceChannel in that sample.

Understanding spring cloud messaging with rabbitmq

I think I have a problem understanding spring cloud messaging and can't find an answer to a "problem" I'm facing.
I have the following setup (using spring-boot 2.0.3.RELEASE).
application.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
cloud:
stream:
bindings:
input:
destination: foo
group: fooGroup
fooChannel:
destination: foo
Service class
#Autowired
FoodOrderController foodOrderController;
#Bean
public CommandLineRunner runner() {
return (String[] args) -> {
IntStream.range(0,50).forEach(e -> foodOrderController.orderFood());
};
}
#StreamListener(target = FoodOrderSource.INPUT)
public void processCheapMeals(String meal){
System.out.println("This was a great meal!: "+ meal);
}
#StreamListener(target = FoodOrderSource.INPUT)
public void processCheapMeals1(String meal){
System.out.println("This was a great meal!: "+ meal);
}
FoodOrderController
public class FoodOrderController {
#Autowired
FoodOrderSource foodOrderSource;
public String orderFood(){
var foodOrder = new FoodOrder();
foodOrder.setCustomerAddress(UUID.randomUUID().toString());
foodOrder.setOrderDescription(UUID.randomUUID().toString());
foodOrder.setRestaurant("foo");
foodOrderSource.foodOrders().send(MessageBuilder.withPayload(foodOrder).build());
// System.out.println(foodOrder.toString());
return "food ordered!";
}
}
FoodOrderSource
public interface FoodOrderSource {
String INPUT = "foo";
String OUTPUT = "fooChannel";
#Input("foo")
SubscribableChannel foo();
#Output("fooChannel")
MessageChannel foodOrders();
}
FoodOrderPublisher
#EnableBinding(FoodOrderSource.class)
public class FoodOrderPublisher {
}
The setup is working, with the exception that both StreamListener receive the same messages. So everything get's logged twice. Reading the documentation, it says specifying a group inside the queues bindings, both the listeners will be registered inside the group and only one listener will receive a single message. I know that the example above is not sensible, but I want to mimic a multi-node environment with multiple listeners setup.
Why is the message received by both listeners? And how can I make sure that a message is only received once within a setup group?
According to the documentation, messages should also be auto-acknowledged by default, but I can't find anything that indicates that the messages actually get acknowledged. Am I missing something here?
Here's some screenshots of rabbit admin
Reading the documentation, it says specifying a group inside the queues bindings, both the listeners will be registered inside the group and only one listener will receive a single message.
That is true when the listeners are in different application instances. When there are multiple listeners in the same instance they all get the same message. This is typically used with a condition where each listener can express interest in which meals they are interested in. Documented here.
Basically, the competing consumer is the binding itself which dispatches the message to the actual #StreamListeners in the application.
So, you can't "mimic a multi-node environment with multiple listeners setup" this way.
but I can't find anything that indicates that the messages actually get acknowledged
What do you mean by that? If the message is processed successfully, the container acks the message and it is removed from the queue.
Thow correct answer is already replied on the post, but you can still look into this:
https://github.com/jinternals/spring-cloud-stream

Publish & Subscribe with Same Connection using Spring Integration MQTT

Due to the design of MQTT where you can only make a connection with a unique client id, is it possible to use the same connection to publish and subscribe in Spring Framework/Boot using Integration?
Taking this very simple example, it would connect to the MQTT broker to subscribe and get messages, but if you would want to publish a message, the first connection will disconnect and re-connect after the message is sent.
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
// publisher
#Bean
public IntegrationFlow mqttOutFlow() {
return IntegrationFlows.from(CharacterStreamReadingMessageSource.stdin(),
e -> e.poller(Pollers.fixedDelay(1000)))
.transform(p -> p + " sent to MQTT")
.handle(mqttOutbound())
.get();
}
#Bean
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("siSamplePublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("siSampleTopic");
return messageHandler;
}
// consumer
#Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.transform(p -> p + ", received from MQTT")
.handle(logger())
.get();
}
private LoggingHandler logger() {
LoggingHandler loggingHandler = new LoggingHandler("INFO");
loggingHandler.setLoggerName("siSample");
return loggingHandler;
}
#Bean
public MessageProducerSupport mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("siSampleConsumer",
mqttClientFactory(), "siSampleTopic");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
return adapter;
}
Working with 2 separate connections becomes difficult if you need to wait for an answer/result after publishing a message...
the first connection will disconnect and re-connect after the message is sent.
Not sure what you mean by that; both components will keep open a persistent connection.
Since the factory doesn't connect the client, the adapters do, it's not designed for using a shared client.
Using a single connection won't really help with coordination of requests/replies because the reply will still come back asynchronously on another thread.
If you have some data in the request/reply that you can use for correlation of replies to requests, you can use a BarrierMessageHandler to perform that task. See my answer here for an example; it uses the standard correlation id header, but that's not possible with MQTT, you need something in the message.
TL;DR
The answer is no, not with the current Spring Boot MQTT Integration implementation (and maybe not even with future ones).
Answer
I'm facing the same exact situation: I need an MQTT Client to be opened in both inbound and outbound, making the connection persistent and sharing the same configuration (client ID, credentials, etc.), using Spring Integration Flows as close to the design as possible.
In order to achieve this, I had to reimplement MqttPahoMessageDrivenChannelAdapter and MqttPahoMessageHandler and a Client Factory.
In both MqttPahoMessageDrivenChannelAdapter and MqttPahoMessageHandler I had to choose to use the Async one (IMqttAsyncClient) in order to fix which one to use. Then I had to review parts of code where the client instance is called/used in order to check if it was already instantiated by the other flow and checking the status (e.g. not trying to connect it if it was already connected).
Regarding the Client Factory, it was easier: I've reimplemented the getAsyncClientInstance(String url, String clientId) using the concatenation of url and clientId as hash as key to store the instance into a map that is used to retrieve the existing instance if the other flow requests it.
It somehow works, but it's just a test and I'm not even sure it's a good approach. (I've started another StackOverflow question in order to track my specific scenario).
Can you share how did you manage your situation?

Spring as Broker Relay by using an external Message Broker

I would like to use Spring Messaging to create a real time notification system for logged users for my webapp.
I defined a AbstractWebSocketMessageBrokerConfigurer as follows:
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notifications").withSockJS()
.setSessionCookieNeeded(true)
.setWebSocketEnabled(true);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic/", "/queue/");
}
And, according to the documentation:
An application can send messages targeting a specific user. Spring’s STOMP support recognizes destinations prefixed with "/user/". For example, a client might subscribe to the destination "/user/queue/position-updates". This destination will be handled by the UserDestinationMessageHandler and transformed into a destination unique to the user session, e.g. "/queue/position-updates-user123". This provides the convenience of subscribing to a generically named destination while at the same time ensuring no collisions with other users subscribing to the same destination so that each user can receive unique stock position updates.
On the sending side messages can be sent to a destination such as "/user/{username}/queue/position-updates", which in turn will be translated by the UserDestinationMessageHandler into one or more destinations, one for each session associated with the user. This allows any component within the application to send messages targeting a specific user without necessarily knowing anything more than their name and the generic destination. This is also supported through an annotation as well as a messaging template.
By sending a message to /user/{username}/queue/something, it will be delivered only to the specific user identified by {username}.
Now, I'm looking for a solution that allows me to use an external Message Broker (for instance, RabbitMQ), with Spring just as Broker Relay:
registry.enableStompBrokerRelay("/topic/", "/queue/");
After configuring the External Message Broker in Spring:
Is it possible to send a message on Message Broker by using as channel /user/{username/}/queue/something? If yes, how?
By sending a message on Message Broker by using as channel /user/{username/}/queue/something, is Spring able to send that message only to {username} according to the current Principal?
Yes it is possible, if you enable an external broker, every #MessageMapping return value will be serialized to JSON and sent to the broker, have a look to the Flow of Messages section of the reference documentation for more details. So it is basically the same that with the simple broker.
You can also inject a SimpMessagingTemplate or SimpMessageSendingOperations bean, like it is done in my OpenSnap example application. You can use this from a Controller, but also from any other class in a pure push context.
You can retrieve the principal by adding a Principal parameter to your #MessageMapping or #SubscribeMapping handler method, like it is done here, the current principal will be automatically injected.

Websocket in Spring and Java

For a project I'm building in Spring I'd like to implement websocket. I have found a solution in the form of STOMP, but I can not find a way to send a websocket message to a single user, only a way to do a full broadcast to all clients. What are good alternatives that plug easily into Spring and that I can use to send and receive messages? I have a self-rolled user system in Spring (rather than using Spring Security) and I want to tie it in with that.
Edit: I'd like to point out that I want a solution that degrades gracefully to other protocols for communication, in the way that socket.io does this.
For using Spring with websockets, have a look at the new Spring Websocket support in Spring 4, this is a presentation about it.
According to the documentation, Spring supports connection a single user, as well as broadcast:
Spring Framework allows #Controller classes to have both HTTP request
handling and WebSocket message handling methods. Furthermore, a Spring
MVC request handling method, or any application method for that
matter, can easily broadcast a message to all interested WebSocket
clients or to a specific user.
This is an example of a broadcast, which you already can do:
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(3000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
According to the documentation, the way to not do broadcast and reply only to the calling customer is to ommit the #SendTo annotation:
By default the return value from an #SubscribeMapping method is sent
as a message directly back to the connected client and does not pass
through the broker. This is useful for implementing request-reply
message interactions;
This should help
private final SimpMessageSendingOperations messagingTemplate;
List<String> messages = new ArrayList<>(Arrays.asList("Bar","Foo"));
#AutoWired
public YourConstructor(SimpMessageSendingOperations messagingTemplate){
this.messagingTemplate = messagingTemplate;
}
#Scheduled(fixedDelay=1500)
public void sendMessages() {
for (String message : messages) {
this.messagingTemplate.convertAndSendToUser(user, "/queue/messages", message);
}
}
PS: #Scheduled Annotation for timed task

Categories

Resources