How to subscribe a client to a rsocket server? - spring-boot

My goal is to subscribe a client to a server events SSE. Clients will use WebSocket protocol, thus I use: spring.rsocket.server.transport: websocket
The analog on blocking IO stack:
#Configuration
#EnableWebSocketMessageBroker
class WebSocketConfiguration: WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
registry.enableSimpleBroker("/topic")
registry.setApplicationDestinationPrefixes("/app")
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/websocket")
.setAllowedOrigins("*")
.withSockJS();
}
}
Usage:
#Service
class MessagingService(private val simpMessagingTemplate: SimpMessagingTemplate) {
private val logger = KotlinLogging.logger {}
fun notify(baseEvent: BaseEvent) {
logger.debug { "Sending an event $baseEvent" }
simpMessagingTemplate.convertAndSend("/topic/events", baseEvent)
}
}
Client code:
function connect() {
var socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/events', function (event) {
console.log('Handled message from ws: ' + event)
showGreeting(JSON.parse(event.body));
});
});
}
Where simpMessagingTemplate pushes an event to connected clients.
I want to achieve the same with reactor and spring-boot-starter-rsocket which works with WebSocket also at client-server level.
Rsocket request-stream fits but in this case, I need eternal flux stream on server cause I don't know when an event will come. I don't know how to do it

Related

WebSocket connection to 'ws://localhost:9955/essence/fecf/maker-checker/notification/notify/272/ruxkoajx/websocket' failed:

We have a spring-boot application which runs on tomcat which has only back end server side code(Java). For the front end UI component runs on a different server and is connected to back end server through rest service calls.
On some event triggered on back end server, the back end server should be able to push some messages to the front end UI component through websockets.
As I do understand websockets are designed for duplex scenarios but for now we do not have a requirement of sending messages from front end UI component to back end server component.
So I tried designing our code as below to send messages from back end server component to the front end UI component.
Back end server side:
Added following piece of code to register stompclient and configure message broker,
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/maker-checker/notification/topic");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/maker-checker/notification/ws").setAllowedOriginPatterns("*");
registry.addEndpoint("/maker-checker/notification/ws").setAllowedOriginPatterns("*").withSockJS();
}
}
And I have created a Notifier class which will be called as a Java API by the events who wants to send messages to front end UI component.
#Service
public class WebSocketNotifier implements Notifier {
private final SimpMessagingTemplate simpMessagingTemplate;
public WebSocketNotifier(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
#Override
public void send(String recipient, String message) {
// TODO Auto-generated method stub
NotificationMessage notificationMessage = new NotificationMessage(recipient, message);
simpMessagingTemplate.convertAndSend("/maker-checker/notification/topic",
JsonHelper.getInstance().writeJsonValue(notificationMessage));
}
}
Front end UI component side
The client looks like,
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new SockJS('http://localhost:9955/essence/fecf/maker-checker/notification/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/maker-checker/notification/topic',
function(messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
});
});
}
But after doing this websocket connection is not being established, getting error
websocket.js:6 WebSocket connection to 'ws://localhost:9955/essence/fecf/maker-checker/notification/notify/272/ruxkoajx/websocket' failed:

Get Response When It is Ready In Spring Boot

I have front-end(script) and back-end(Spring-Boot) code.
In backend code:
#GetMapping("/calldata")
public Response call() {
...//Imagine this operations take more than 5 minutes.
}
In font-end code:
I just call this backend api and open socket and wait until data is ready in loading state.
Is there a way to say from backend to frontend; "Don't wait to me. I will notify to you when I am ready. And I will serve my data."?
You want you request to be handled asynchronously. You can use websockets which keeps a single persistent connection open between server and client.
https://www.baeldung.com/spring-websockets-sendtouser
I had the same problem and my solution include a combination of WebSocket and Async programming. The good thing about this approach is, you can still call your REST endpoint normally. I am using SpringBoot and Angular 9. Here is what I did:
Create an async service on BE
Create WebSocket on BE
Create WebSocket on FE
Create a common topic and let FB and BE listen to it, where BE will push the response and FE and read from it.
Create a void controller method and call the async service's method
a. By doing this, your FE will not wait for the server response and your async service can continue to process the request.
Once your service is done processing, push the response to a websocket topic
Listen to the topic on your FE, and once BE pushes the response you'll be able to handle it on FE.
Here is the sample code:
Index.html:
<script>
var global = global || window;
var Buffer = Buffer || [];
var process = process || {
env: { DEBUG: undefined },
version: []
};
</script>
FE WebSocket congif file:
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
export class WebSocketAPI {
// localWebSocketEndpoint = 'http://localhost:8080/ws';
webSocketEndpoint = '/ws';
topic = '/topic/greetings'; // this is the topic which will be used to exchagne data
stompClient: any;
constructor() { }
connect() {
let ws = new SockJS(this.webSocketEndpoint);
this.stompClient = Stomp.over(ws);
const that = this;
that.stompClient.connect({}, function (frame) {
that.stompClient.subscribe(that.topic, function (sdkEvent) {
that.onMessageReceived(sdkEvent);
})
})
}
disconnect() {
if (this.stompClient !== null) {
this.stompClient.disconnect();
}
}
// you don't need this
send(name) {
this.stompClient.send('/app/hello', {}, JSON.stringify({name: name}));
}
// this is where you will receive your data once Server is done process
onMessageReceived(message) {
console.log('received: ', message);
// this.app.handleMessage(message.body);
}
}
BE Controller method:
#GetMapping("/calldata")
#ResponseStatus(value = HttpStatus.OK)
#LogExecutionTime
public void call() {
asyncService.processAsync();
}
AsyncService:
#Service
public class AsyncService {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#LogExecutionTime
#Async("asyncExecutor")
public void processAsync() {
// do your processing and push the response to the topic
simpMessagingTemplate.convertAndSend("/topic/greetings", response);
}
}
WebSocketConfig:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
And finally AsyncConfig:
#Configuration
#EnableAsync
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
return executor;
}
}
Hope this will help you as well.

Spring stomp over websocket SubscribeMapping not working

I'm trying to configure subscription mapping for stomp over websockets in a spring boot application without any luck. I'm fairly certian I have the stomp/websocket stuff configured correctly as I am able to subscribe to topics that are being published to by a kafka consumer, but using the #SubscribeMapping is not working at all.
Here is my controller
#Controller
class TestController {
#SubscribeMapping("/topic/test")
fun testMapping(): String {
return "THIS IS A TEST"
}
}
And here is my configuration
#Configuration
#EnableWebSocketMessageBroker
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
class WebSocketConfig : AbstractWebSocketMessageBrokerConfigurer() {
override fun configureMessageBroker(config: MessageBrokerRegistry) {
config.setApplicationDestinationPrefixes("/app", "/topic")
config.enableSimpleBroker("/queue", "/topic")
config.setUserDestinationPrefix("/user")
}
override fun registerStompEndpoints(registry:StompEndpointRegistry) {
registry.addEndpoint("/ws").setAllowedOrigins("*")
}
override fun configureClientInboundChannel(registration: ChannelRegistration?) {
registration?.setInterceptors(object: ChannelInterceptorAdapter() {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> {
val accessor: StompHeaderAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
if (StompCommand.CONNECT.equals(accessor.command)) {
Optional.ofNullable(accessor.getNativeHeader("authorization")).ifPresent {
val token = it[0]
val keyReader = KeyReader()
val creds = Jwts.parser().setSigningKey(keyReader.key).parseClaimsJws(token).body
val groups = creds.get("groups", List::class.java)
val authorities = groups.map { SimpleGrantedAuthority(it as String) }
val authResult = UsernamePasswordAuthenticationToken(creds.subject, token, authorities)
SecurityContextHolder.getContext().authentication = authResult
accessor.user = authResult
}
}
return message
}
})
}
}
And then in the UI code, I'm using angular with a stompjs wrapper to subscribe to it like this:
this.stompService.subscribe('/topic/test')
.map(data => data.body)
.subscribe(data => console.log(data));
Subscribing like this to topics that I know are emitting data works perfectly but the subscribemapping does nothing. I've also tried adding an event listener to my websocket config to test that the UI is actually sending a subscription event to the back end like this:
#EventListener
fun handleSubscribeEvent(event: SessionSubscribeEvent) {
println("Subscription event: $event")
}
#EventListener
fun handleConnectEvent(event: SessionConnectEvent) {
println("Connection event: $event")
}
#EventListener
fun handleDisconnectEvent(event: SessionDisconnectEvent) {
println("Disconnection event: $event")
}
Adding these event listeners I can see that all the events that I'm expecting from the UI are coming through in the kotlin layer, but my controller method never gets called. Is there anything obvious that I'm missing?
Try the following:
#Controller
class TestController {
#SubscribeMapping("/test")
fun testMapping(): String {
return "THIS IS A TEST"
}
}

Sending message to specific user not working in STOMP, sockjs, spring

I have an issue with Spring and Stomp, the messages are not being received by the client. I have tried everything and not sure what can be causing it!
I have the following set up so far. The first subscribe /event/{eventId} works fine and is receiving messages. The specific user subscription reaches the controller but the response from convertandsendtouser is not being received. Any ideas?
function connect() {
var socket = new SockJS('/events');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/' + eventIdStomp, function(messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
alert("Subscribed to event id");
});
stompClient.subscribe("/user/queue/reply", function(responseOutput) {
alert("Hey it worked, subscribed to user queue");
showMessageOutput(JSON.parse(responseOutput.body));
});
});
}
function sendMessage() {
var text = document.getElementById('text').value;
stompClient.send("/app/events/" + eventIdStomp, {},
JSON.stringify({'from':from, 'text':text}));
stompClient.send("/app/events/personal", {},
JSON.stringify({'from':from, 'text':text}));
}
and on the server side
#MessageMapping("/events/personal")
public void personal(Message message, Principal principal) throws Exception {
System.out.println("im in side personal methods");
String time = new SimpleDateFormat("HH:mm").format(new Date());
/* Set author */
User user = (User) ((Authentication) principal).getPrincipal();
if(user!=null) {
System.out.println("in inside user id");
/* Check message content in Knowledge Base */
// If there is any indication that the message contains material against the code of conduct,
// then a message should be sent to that person only and not to everybody.
OutputMessage custom_response = new OutputMessage(user.getUsername(), "I can see you...", time);
simpMessagingTemp.convertAndSendToUser(user.getUsername(), "/queue/reply", custom_response);
// End of KB
System.out.println("after mnessage sent");
}
}
with the config
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/* RabbitMQ As Broker, using CloudAMQP as a cloud service */
config.enableStompBrokerRelay("/queue", "/topic").setRelayHost("swan.rmq.cloudamqp.com")
/* System admin login */
.setSystemLogin("yyy").setSystemPasscode("xxx")
.setVirtualHost("yyy")
/* for presentation purposes, client can login as system admin */
.setClientLogin("yyy").setClientPasscode("xxx");
config.setApplicationDestinationPrefixes("/app");
}
/*
* When we create a connection from client, this is the URL which clients
* connect to Websocket URL prefix
*/
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/events").withSockJS().setSessionCookieNeeded(true);
}
}

WebSocket can connect to local server, but can't send or receive

I'm working on setting up a websocket between two local servers for development.
On one end I have my Ionic app running on http://localhost:8100/
On the other end I have a Spring backend running on http://localhost:9080/ (or http://127.0.0.1:9080)
Connection has been established, so next up I want to send a message to the websocket with a token (I know this can be send along when the connection is set up in SockJS 1.1.0, but I am currently using 0.3.4)
However my code in the backend doesn't seem to respond, I am wondering if my IP configuration is correct. I followed a tutorial and got this working in another project.
Anyone with more experience that knows if the url in the subscribe function also needs to be prefixed with 'localhost' or an IP adrress? I know the websocket changes from http:// to ws:// so I suppose when this is the case I need to prefix it with something like: ws://localhost:9080/...
Anyway, here's my code:
WebSocet Service :
function init() {
var socket = new SockJS('http://127.0.0.1:9080/ws-notification');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
/**
* Subscribe at /ws-topic/greetings url to catch messages
*/
stompClient.subscribe('/ws-topic/greetings', function(greeting){
notify(JSON.parse(greeting.body).content);
});
parseAuthentication();
});
}
function parseAuthentication(){
stompClient.send("/ws-app/ws-notification",{},JSON.stringify({ 'token': authenticationService.isAuthenticated() }));
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
// setConnected(false);
console.log("Disconnected");
}
function notify(message){
console.log("NOTIFY: "+message);
}
The WebSocket Config :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-notification").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config){
config.enableSimpleBroker("/ws-topic");
config.setApplicationDestinationPrefixes("/ws-app");
}
}
My Controllerfunction :
#MessageMapping("/ws-notification")
#SendTo("/ws-topic/greetings")
public Notify greeting(Notify notify) throws InterruptedException {
Thread.sleep(1000);
return new Notify("Hello, your token is :" + notify.getWsToken());
}
Notice that I only specify the IP adress when I set up the connection in the init() function, tried to prefix the other url's with ws://127.0.0.1:... but no luck!
I found the answer!
The problem was that there was no default contructor method in the models I used to send the data.
This was also not implemented or mentioned in the Spring WebSocket Tutorial

Resources