I want to pass the connected socket client to nest js service. I referred to this question
https://stackoverflow.com/question.....but I can't understand how to pass the client:Socket from my service.
I have a bull queue which handles some file processing tasks. once the file processing is completed. I want to notify that to the user who sent that file. below is my WebSocket gateway. In here notifyJobStatus is getting called once the job is done. but I want to send it to a particular client. for that, I need to access the client from my service. then I can modify the method to something like this notifyJobStatus(status: string, client: Socket).please correct me if this approach is wrong.
export class NotificationsService
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
#WebSocketServer() server: Server;
private readonly logger = new Logger(NotificationsService.name);
notifyJobStatus(status: string) {
//client.broadcast.to(client.id).emit('JobStatus', status);
this.server.emit('JobStatus', status);
}
afterInit() {
this.logger.log('Websocket Server Started,Listening on Port:3006');
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
console.log(`Client connected: ${client.id}`);
}
}
If this is your gateway, I guess it is not setup properly. Gateway in nestjs takes the form below:
#WebSocketGateway()
export class NotificationsService
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
#WebSocketServer() server: Server;
private readonly logger = new Logger(NotificationsService.name);
#SubscribeMessage('event to listen to')
notifyJobStatus(status: string) {
//client.broadcast.to(client.id).emit('JobStatus', status);
this.server.emit('JobStatus', status);
}
afterInit() {
this.logger.log('Websocket Server Started,Listening on Port:3006');
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
console.log(`Client connected: ${client.id}`);
}
}
Meanwhile, since you are using a bull queue to process the job, you can emit the job status from the bull queue consumer that processes the job, which #SubscribeMessage('event to listen to') will listen on, and the notifyJobStatus method will be called to send the status to the user.
Related
I have a Spring Boot application, implementing Websocket as well as Redis stream.
The flow is, Subscriber (who subscribes Redis stream) upon receiving message will then send that information into Websocket (using STOMP protocol and AmazonMQ - ActiveMQ as an external message broker).
Example of 1 consumer group
public Subscription fExecutionResponse(RedisTemplate<String, Object> redisTemplate) {
try {
String groupName = FStreamName.fExecutionConsumerGroup;
String streamKey = FStreamName.fExecutionStream;
createConsumerGroup(streamKey, groupName, redisTemplate);
val listenerContainer = listenerContainer(redisTemplate, FTradeDataRedisEvent.class);
val subscription = listenerContainer.receiveAutoAck(
Consumer.from(groupName, FStreamName.fExecutionConsumer),
StreamOffset.create(streamKey, ReadOffset.lastConsumed()),
message -> {
log.info("[Subscription F_EXECUTION]: {}", message.getValue());
FTradeDataRedisEvent mTradeEvent = message.getValue();
try {
if (ExternalConfiguration.futureTradeDataSource.equals(ExecutionSource.ALL) || ExternalConfiguration.futureTradeDataSource.equals(mTradeEvent.getSource())) {
futureProductService.updateProductByDummy(mTradeEvent);
futureExecutionToTickHistoricalService.transform(mTradeEvent);
}
} catch (Exception ex) {
log.error("[fTradeEventResponse] error: {}", ex.getMessage());
}
redisTemplate.opsForStream().acknowledge(streamKey, groupName, message.getId());
redisTemplate.opsForStream().delete(streamKey, message.getId());
});
listenerContainer.start();
log.info("A stream key `{}` had been successfully set up in the group `{}`", streamKey, groupName);
return subscription;
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
return null;
}
futureExecutionToTickHistoricalService.transform will send the data to web socket using SimpMessageSendingOperations
public void transform(FTradeDataRedisEvent tradeData) {
if (lastUpdateTickHistoricalDataTime == 0L) {
Calendar calendar = Calendar.getInstance();
this.lastUpdateTickHistoricalDataTime = calendar.getTimeInMillis();
}
List<FTickHistorical> res = separateFTickHistorical(tradeData);
res.forEach(tickHistorical -> {
List<KlineResponse> klineResponses = new ArrayList<>();
klineResponses.add(new KlineResponse(tickHistorical));
messageTemplate.convertAndSend(
PUBLIC_TOPIC_PREDIX + Constants.F_PRODUCTS_DESTINATION + "/" + tickHistorical.getProductId() + "/klines" + "_" + tickHistorical.getResolution().getCode(),
new HistoryResponse(klineResponses)
);
});
}
There are two problems with this setup, I have resolved one of them.
The Redis stream subscriber is started up before the connection to the external message broker is ready. Solved (listen to BrokerAvailabilityEvent and only then start Redis subscriptions)
When redeploy or shutdown application on IDE (like Intellij). The connection to the broker is again destroyed first (before the Redis stream subscribers), at the same time, there are still some data sending to the socket. This cause error: Message broker not active
I don't know how to configure the Spring boot application, so that when the application is stopped, it first stops consuming messages from Redis stream, process all of the pending messages, then close the broker connection.
This is the error log when the application is destroyed.
14.756 INFO 41184 --- [extShutdownHook] c.i.c.foapi.SpotStreamSubscriber : Broker not available
2022-10-09 14:14:14.757 INFO 41184 --- [extShutdownHook] c.i.c.f.config.RedisSubConfiguration : Broker not available
2022-10-09 14:14:14.781 ERROR 41184 --- [cTaskExecutor-1] c.i.c.foapi.consumer.SpotStreamConsumer : [executionResponse] error: Message broker not active. Consider subscribing to receive BrokerAvailabilityEvent's from an ApplicationListener Spring bean.; value of message: ExecutionRedisEvent(id=426665086, eventTime=2022-10-09T14:13:45.056809, productId=2, price=277.16, quantity=0.08, buyerId=2, sellerId=3, createdDate=2022-10-09T14:13:45.056844, takerSide=SELL, orderBuyId=815776680, orderSellId=815776680, symbol=bnbusdt, source=BINANCE)
2022-10-09 14:14:14.785 ERROR 41184 --- [cTaskExecutor-1] c.i.c.foapi.consumer.SpotStreamConsumer : [dummyOrderBookResponse] error: Message broker not active. Consider subscribing to receive BrokerAvailabilityEvent's from an ApplicationListener Spring bean.; value of message: DummyOrderBookEvent(productId=1, bids=[DummyOrderBook(price=1941.6, qty=0), DummyOrderBook(price=18827.3, qty=0.013), DummyOrderBook(price=18938.8, qty=5.004), DummyOrderBook(price=18940.3, qty=22.196), DummyOrderBook(price=18982.5, qty=20.99), DummyOrderBook(price=19027.2, qty=0.33), DummyOrderBook(price=19045.8, qty=8.432)
This is the code of SpotStreamSubscriber
#Component
#RequiredArgsConstructor
#Slf4j
public class SpotStreamSubscriber implements ApplicationListener<BrokerAvailabilityEvent> {
private final SpotStreamConsumer spotStreamConsumer;
#Override
public void onApplicationEvent(BrokerAvailabilityEvent event) {
if (event.isBrokerAvailable()) {
log.info("Broker ready");
spotStreamConsumer.subscribe();
} else {
log.info("Broker not available");
}
}
}
As you can see, the message broker is destroyed before the pending Redis messages have a chance to be processed.
Current architecture,
We use external message broker so that we can scale horizontally the api.
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
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.
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
I have some web services exposed using spring web services.
I would like to set a maximun timeout on server side, I mean, when a client invokes my web service It could not last more than a fixed time. Is it possible?
I have found lot of information about client timeouts, but not server timeout.
This is set at the level of the server itself and not the application, so it's application server dependent.
The reason for this is that it's the server code that opens the listening socket used by the HTTP connection, so only the server code can set a timeout by passing it to the socket API call that starts listening to a given port.
As an example, this is how to do it in Tomcat in file server.xml:
<Connector connectionTimeout="20000" ... />
You can work around this issue by making the web service server trigger the real work on another thread and countdown the time out it self and return failure if timed out.
Here is an example of how you can do it, it should time out after 10 seconds:
public class Test {
private static final int ONE_SECOND = 1_000;
public String webserviceMethod(String request) {
AtomicInteger counter = new AtomicInteger(0);
final ResponseHolder responseHolder = new ResponseHolder();
// Create another thread
Runnable worker = () -> {
// Do Actual work...
responseHolder.finished = true;
responseHolder.response = "Done"; // Actual response
};
new Thread(worker).start();
while (counter.addAndGet(1) < 10) {
try {
Thread.sleep(ONE_SECOND);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (responseHolder.finished) {
return responseHolder.response;
}
}
return "Operation Timeout"; // Can throw exception here
}
private final class ResponseHolder {
private boolean finished;
private String response; // Can be any type of response needed
}
}