Scheduled websocket push with Springboot - java

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

Related

Spring Websocket ChannelInterceptor not firing CONNECT event

I'm writing simple Stomp Websocket application with Spring, and clients are both web (JS), and Mobile (ios, android). From JS code client connecting over SockJS, while mobile clients are using plain websocket connection behind SockJS.
The issue is that behaviour in my ChannelInterceptor where I'm checking authentication, is completely different for different type of connections. I can't make it work the same for every client.
Let me briefly give some code behind it and explain by example:
Websocket starter was taken from Spring example here: https://github.com/spring-guides/gs-messaging-stomp-websocket.git
Websocket Config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new MyChannelInterceptor());
}
}
And ChannelInterceptor itself:
public class MyChannelInterceptor implements ChannelInterceptor {
#Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
StompCommand command = accessor.getCommand();
...
}
}
When I'm connecting via SockJS from JS app (http://localhost:8080/gs-guide-websocket - and let Spring SockJS do the rest):
I can catch CONNECT command in MyChannelInterceptor, in postSend method - OK
When I close the connection, in the same place DISCONNECT command fires TWICE. - Not OK
When I'm connecting via Websocket behind SockJS (ws://localhost:8080/gs-guide-websocket/websocket):
I CAN'T catch CONNECT command in MyChannelInterceptor, in postSend method - CRITICAL
When I close the connection, DISCONNECT command fires correctly, once. - OK
Basically, though I can't understand why sockjs tries to disconnect twice, I can live with it.
But with interceptor not catching every connect event - I can't live, since I'm going to keep track of user session, and store them from exactly that interceptor.
I've already tried to remove .withSockJs() in the config - and just connect to socket - same problem
I've also tried to implement application event listener on SessionConnectEvent and SessionConnectedEvent - same problem
Now I'm completely stuck and don't know where else I can go from here...
Any help or starting point is appreciated.
Thanks a lot for any attention to my pain =(
After posting an issue to Spring Github and conversating there, I found out that this is not a bug, and basically not an issue, but just my fault:
The behavior for DISCONNECT is expected. It is mentioned in several places in the Spring WebSocket documentation, under Interception, Events, and Monitoring chapters.
CONNECT event is not expected to be fired when connecting via plain Websockets, cause it is just establishing connecting over plain WebSocket protocol, and for STOMP events you need to use STOMP client.
For those interested, please refer to the corresponding thread:
https://github.com/spring-projects/spring-framework/issues/24269

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 application server to client data transmission via websocket

Question : Is it possible to send data from server to client in spring ,using websocket, without creating another client , in the java app that sustains the server ?
Almost every article on the internet about websocket in spring, shows how to define your methods in the controller for handling requests . All the magic seem to happen when they define a function with the 2 annotations #MessageMapping("/news") , #SendTo("/topic/news") . By having this method alone , as far as I know, you can only catch requests and send them further ( SendTo) . I want to know if it is possible to send data from server to client without having a client requesting something in the first place. And how that code would look like.
you could use SimpMessagingTemplate .
i think you should have this class somewhere
#Configuration
#EnableWebSocketMessageBroker
public class "yournameclass" extends AbstractWebSocketMessageBrokerConfigurer
then this method inside
#Override
public void configureMessageBroker(MessageBrokerRegistry configuration) {
configuration.enableSimpleBroker("/test");
configuration.setApplicationDestinationPrefixes("/someprefix");
}
then you could call this from some method in your code
#Autowired
private SimpMessagingTemplate template;
public void "mymethodsender" ()
{ this.template.convertAndSend("/test/somepathwhereyouregisteredyourclienttoreceivemessages", "messageobject");
}

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.

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