Websocket in Spring and Java - 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

Related

Spring Cloud Stream doesn't use Kafka channel binder to send a message

I'm trying to achieve following thing:
Use Spring Cloud Stream 2.1.3.RELEASE with Kafka binder to send a message to an input channel and achieve publish subscribe behaviour where every consumer will be notified and be able to handle a message sent to a Kafka topic.
I understand that in Kafka if every consumer belongs to its own consumer group, will be able to read every message from a topic.
In my case spring creates an anonymous unique consumer group to every instance of my spring boot application running. The spring boot application has only one stream listener configured to listen to the input channel.
Test example case:
Configured an example Spring Cloud Stream app with an input channel which is bound to a Kafka topic.
Using a spring rest controller to send a message to the input channel expecting that message will be delivered to every spring boot application instance running.
In both applications on startup I can see that kafka partition is assigned properly.
Problem:
However, when I send a message using output().send() then spring doesn't even send a message to the Kafka topic configured, instead, in the same thread it triggers #StreamListener method of the same application instance.
During debugging I see that spring code has two handlers of the message. The StreamListenerMessageHandler and KafkaProducerMessageHandler.
Spring simply chains them and if first handler ends with success then it will not even go further. StreamListenerMessageHandler simply invokes my #StreamListener method in the same thread and message never reaches Kafka.
Question:
Is this by design, and in that case why is that? How can I achieve behaviour mentioned at the beginning of the post?
PS.
If I use KafkaTemplate and #KafkaListener method then it works as I want. Message is sent to Kafka topic and both application instances receive message and handles it in Kafka listener annotated method.
Code:
The stream listener method is configured the following way:
#SpringBootApplication
#EnableBinding(Processor.class)
#EnableTransactionManagement
public class ProcessorApplication {
private Logger logger =
LoggerFactory.getLogger(this.getClass().getName());
private PersonRepository repository;
public ProcessorApplication(PersonRepository repository) {
this.repository = repository;
}
public static void main(String[] args) {
SpringApplication.run(ProcessorApplication.class, args);
}
#Transactional
#StreamListener(Processor.INPUT)
#SendTo(Processor.OUTPUT)
public PersonEvent process(PersonEvent data) {
logger.info("Received event={}", data);
Person person = new Person();
person.setName(data.getName());
logger.info("Saving person={}", person);
Person savedPerson = repository.save(person);
PersonEvent event = new PersonEvent();
event.setName(savedPerson.getName());
event.setType("PersonSaved");
logger.info("Sent event={}", event);
return event;
}
}
Sending a message to the input channel:
#RestController()
#RequestMapping("/persons")
public class PersonController {
#Autowired
private Sink sink;
#PostMapping("/events")
public void createPerson(#RequestBody PersonEvent event) {
sink.input().send(MessageBuilder.withPayload(event).build());
}
}
Spring Cloud Stream config:
spring:
cloud.stream:
bindings:
output.destination: person-event-output
input.destination: person-event-input
sink.input().send
You are bypassing the binder altogether and sending it directly to the stream listener.
You need to send the message to kafka (to the person-event-input topic) and then each stream listener will receive the message from kafka.
You need to configure another output binding and send it there, not directly to the input channel.

Spring Remoting with AMQP - Client is not seeing the beans exposed from Server

I'm trying to run example from http://www.baeldung.com/spring-remoting-amqp, even when I set up the connection to the dedicated vhost to my RabbitMQ broker, I can only send the request from client (I see it in RabbitMQ UI), but I never get the answer from the server.
The server seems to bean the service (the returning Impl class) with getBeanDefinitionNames(), but I definitly do not see those beans on the client side. I use annotations to set up beans, not the .xml file.
So the question is - why my client is not seeing the Server beans, I discover it more a less in following way:
#Autowired
private ApplicationContext appContext;
public GetResponse get(String id) {
Service service = appContext.getBean(Service.class);
System.out.println(service.ping());
return new GetResponse();
}
The answer which I get on the level of webservice is:
{
"timestamp": "2018-02-01T10:09:00.809Z",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.remoting.RemoteProxyFailureException",
"message": "No reply received from 'toString' with arguments '[]' - perhaps a timeout in the template?",
"path": "/v3/app/r"
}
Service:
public interface Service extends Serializable{
String ping();
}
Service Impl:
public class ServiceImpl implements Service {
#Override
public String ping() {
System.out.println("ponged");
return "pong";
}
#Override
public String toString() {
return "to string";
}
EDITED + BOUNTY
In the link you can find extracted modules which I want to connect together. I suppose that it is still about 'not seeing' the beans from one module in the second one.
The action can be trigerd with GET http://localhost:8081/v3/app/u The RabbitMQ settings has to be adjusted to your set-up.
https://bitbucket.org/herbatnic/springremotingexample/overview
I think you shouldn't set the routing key in your client, in amqpFactoryBean (and the one you set seems invalid):
https://bitbucket.org/herbatnic/springremotingexample/src/b1f08a5398889525a0b1a439b9bb4943f345ffd1/Mod1/src/main/java/simpleremoting/mod1/messaging/Caller.java?at=master&fileviewer=file-view-default
Did you try to run their example?
https://github.com/eugenp/tutorials/tree/master/spring-remoting/remoting-amqp
Just stumbled upon this question 3 years later.. trying to run the Baeldung example!
I tried debugging the issue and as far as I can tell, something internal in the AMQP implementation of spring remoting is not using the correct Routing Key when sending the client message, meaning the payload arrives at the broker and is never put into the queue for processing, we then timeout after 5s (default) on the client.
I tried the other answer by Syl to remove the routingKey however it doesn't seem to allow us to create a binding without one, and even when creating a binding directly on the broker management page (without a routing key) it doesn't route the messages.
I have not managed to make the example work, however I found a blog post on fatalerrors.org that shows a custom implementation of the AmqpProxyFactoryBean and it has custom handling for the routing key, this one works.
I've create this gist with the example that is working for me in case the blog post above goes under.
One other thing to note is that on the Baeldung example they are using a DirectExchange, while here we are using a TopicExchange.

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?

Scheduled websocket push with Springboot

I want to create a simple news feed feature on the front end that will automatically update through websocket push notifications.
The technologies involved are:
Angular for the general front-end application
SockJS for creating websocket communication
Stomp over webosocket for receiving messages from a message broker
Springboot Websockets
Stomp Message Broker (the java related framework)
What I want to achieve on the front end is:
Create a websocket connection when the view is loaded
Create s stomp provider using that websocket
Have my client subscribe to it
Catch server pushed messages and update the angular view
As far as the server side code:
Configure the websocket stuff and manage the connection
Have the server push messages every X amount of time (through an executor or #Scheduled?).
I think I have achieved everything so far except the last part of the server side code. The example I was following uses the websocket in full duplex mode and when a client sends something then the server immediately responds to the message queue and all subscribed clients update. But what I want is for the server itself to send something over Stomp WITHOUT waiting for the client to make any requests.
At first I created a spring #Controller and added a method to it with #SendTo("/my/subscribed/path") annotation. However I have no idea how to trigger it. Also I tried adding #Scheduled but this annotation works only on methods with void return type (and I'm returning a NewsMessage object).
Essentially what I need is to have the client initialize a websocket connection, and after have the server start pushing messages through it at a set interval (or whenever an event is triggered it doesn't matter for now). Also, every new client should listen to the same message queue and receive the same messages.
Before starting, make sure that you have the websocket dependencies in your pom.xml. For instance, the most important one:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework-version}</version>
</dependency>
Then, you need to have your configuration in place. I suggest you start with simple broker.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
}
Then your controller should look like this. When your AngularJs app opens a connection on /portfolio and sends a subscription to channel /topic/greeting, you will reach the controller and respond to all subscribed users.
#Controller
public class GreetingController {
#MessageMapping("/greeting")
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
With regard to your scheduler question, you need to enable it via configuration:
#Configuration
#EnableScheduling
public class SchedulerConfig{}
And then schedule it:
#Component
public class ScheduledUpdatesOnTopic{
#Autowired
private SimpMessagingTemplate template;
#Autowired
private final MessagesSupplier messagesSupplier;
#Scheduled(fixedDelay=300)
public void publishUpdates(){
template.convertAndSend("/topic/greetings", messagesSupplier.get());
}
}
Hope this somehow clarified the concept and steps to be taken to make things work for you.
First of all you can't send (push) messages to clients without their subscriptions.
Secondly to send messages to all subscribers you should take a look to the topic abstraction side.
That is a fundamentals of STOMP.
I think you are fine with #Scheduled, but you just need to inject SimpMessagingTemplate to send messages to the STOMP broker for pushing afterwards.
Also see Spring WebSockets XML configuration not providing brokerMessagingTemplate

Spring user destinations

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

Categories

Resources