Get Response When It is Ready In Spring Boot - 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.

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:

How to subscribe a client to a rsocket server?

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

spring integration publish subscribe between beans

Thanks for reading ahead of time. In my main method I have a PublishSubscribeChannel
#Bean(name = "feeSchedule")
public SubscribableChannel getMessageChannel() {
return new PublishSubscribeChannel();
}
In a service that does a long running process it creates a fee schedule that I inject the channel into
#Service
public class FeeScheduleCompareServiceImpl implements FeeScheduleCompareService {
#Autowired
MessageChannel outChannel;
public List<FeeScheduleUpdate> compareFeeSchedules(String oldStudyId) {
List<FeeScheduleUpdate> sortedResultList = longMethod(oldStudyId);
outChannel.send(MessageBuilder.withPayload(sortedResultList).build());
return sortedResultList;
}
}
Now this is the part I'm struggling with. I want to use completable future and get the payload of the event in the future A in another spring bean. I need future A to return the payload from the message. I think want to create a ServiceActivator to be the message end point but like I said, I need it to return the payload for future A.
#org.springframework.stereotype.Service
public class SFCCCompareServiceImpl implements SFCCCompareService {
#Autowired
private SubscribableChannel outChannel;
#Override
public List<SFCCCompareDTO> compareSFCC(String state, int service){
ArrayList<SFCCCompareDTO> returnList = new ArrayList<SFCCCompareDTO>();
CompletableFuture<List<FeeScheduleUpdate>> fa = CompletableFuture.supplyAsync( () ->
{ //block A WHAT GOES HERE?!?!
outChannel.subscribe()
}
);
CompletableFuture<List<StateFeeCodeClassification>> fb = CompletableFuture.supplyAsync( () ->
{
return this.stateFeeCodeClassificationRepository.findAll();
}
);
CompletableFuture<List<SFCCCompareDTO>> fc = fa.thenCombine(fb,(a,b) ->{
//block C
//get in this block when both A & B are complete
Object theList = b.stream().forEach(new Consumer<StateFeeCodeClassification>() {
#Override
public void accept(StateFeeCodeClassification stateFeeCodeClassification) {
a.stream().forEach(new Consumer<FeeScheduleUpdate>() {
#Override
public void accept(FeeScheduleUpdate feeScheduleUpdate) {
returnList new SFCCCompareDTO();
}
});
}
}).collect(Collectors.toList());
return theList;
});
fc.join();
return returnList;
}
}
Was thinking there would be a service activator like:
#MessageEndpoint
public class UpdatesHandler implements MessageHandler{
#ServiceActivator(requiresReply = "true")
public List<FeeScheduleUpdate> getUpdates(Message m){
return (List<FeeScheduleUpdate>) m.getPayload();
}
}
Your question isn't clear, but I'll try to help you with some info.
Spring Integration doesn't provide CompletableFuture support, but it does provide an async handling and replies.
See Asynchronous Gateway for more information. And also see Asynchronous Service Activator.
outChannel.subscribe() should come with the MessageHandler callback, by the way.

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);
}
}

SelfHost Websocket - Web API vs OWIN Middleware

I'm running an OWIN self-hosted application that hosts a REST API and also allows websocket connectivity for real-time data. I'm using WebAPI to handle the routing and mapping of routes to controllers.
When I use Web API to handle the websocket routes the socket is closed as soon as the controller returns. However, if I create my own middleware the socket does not close.
I'd prefer to use Web API for all of my routes. But more importantly I want to understand what's going on. I don't like my production code to work without understanding why it's working.
Here is the relevant Web API code snippet:
public class WebServer : IDisposable
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "Websocket",
routeTemplate: "ws/all",
defaults: new { controller = "MyWebSocket", action = "Get" });
app.UseWebApi(config);
}
}
public class MyWebSocketController : System.Web.Http.ApiController
{
public IHttpActionResult Get()
{
var owinContext = Request.GetOwinContext();
var accept = owinContext.Get<Action<IDictionary<string, object>, Func<IDictionary<string, object>, Task>>>("websocket.Accept");
accept(null, RunWebSocket);
return Ok();
}
private async Task RunWebSocket(IDictionary<string, object> websocketContext)
{
WebSocket socket;
if (websocketContext.TryGetValue(typeof(System.Net.WebSockets.WebSocketContext).FullName, out value))
{
socket = ((System.Net.WebSockets.WebSocketContext)value).WebSocket;
}
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[128]);
WebSocketReceiveResult result = null;
using (var ms = new MemoryStream())
{
while (socket.State == WebSocketState.Open)
{
ms.SetLength(0);
do
{
result = await socket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
if (result.MessageType == WebSocketMessageType.Close)
{
// Close socket
}
ms.Seek(0, SeekOrigin.Begin);
if (result.MessageType == WebSocketMessageType.Text)
{
// Handle message
}
}
}
}
}
The call to ReceiveAsync schedules a continuation. The Get method returns back to the ApiController which closes the connection, which also closes the websocket.
Here is the relevant code for the OWIN middleware.
public class WebServer : IDisposable
{
public void Configuration(IAppBuilder app)
{
app.Use<WebSocketMiddleware>();
}
}
public class WebSocketMiddleware : OwinMiddleware
{
public override Task Invoke(IOwinContext context)
{
var accept = context.Get<Action<IDictionary<string, object>, Func<IDictionary<string, object>, Task>>>("websocket.Accept");
accept(null, RunWebSocket);
return;
}
private async Task RunWebSocket(IDictionary<string, object> websocketContext)
{
// Same as Web API implementation
}
}
Again the continuation is scheduled during the call to ReceiveAsync and the Invoke method returns. However, the connection remains open and I'm able to send and receive data through the websocket.
So, I have a solution, but I'd really love to understand what's going on. Any references would be greatly appreciated.
Edit: Actually the socket is closed in both cases. The web API version sends a RST from the server, as if the connection was abruptly closed, while the OWIN version experiences a normal FIN ACK. However the web API doesn't allow any further communication over the websocket, while the OWIN version does. So I'm not really sure how this is supposed to work.

Resources