Getting 503 error when SSE send subscribe request event second time - java

Hi when am loading the page the first subscription request for SSE is working fine.
but when SSE timeout and go for subscribe again am getting 503 error.
My backend code:
#RestController
#Slf4j
public class UpdateNotification {
#Autowired
SseService sseService;
#GetMapping(value = "/api/v1/item/subscription", consumes = MediaType.ALL_VALUE, produces = MediaType.ALL_VALUE)
public SseEmitter subscribe(#RequestParam("id") long id) throws InterruptedException, IOException {
SseEmitter emitter = new SseEmitter();
sseService.add(id, emitter);
emitter.onCompletion(() -> sseService.remove(id, emitter));
emitter.onError((ex)->log.info("Error>> "+ ex.getMessage()));
return emitter;
}
#Async
public void produce(#RequestBody final MessageDTO message) {
GetData(message);
}
public void GetData(final MessageDTO message) {
sseService.getSsEmitters(message.getItemDTO().getId()).forEach((SseEmitter emitter) -> {
try {
emitter.send(message.getPrice(), MediaType.APPLICATION_JSON);
} catch (IOException e) {
emitter.complete();
sseService.remove(message.getId(), emitter);
e.printStackTrace();
}
});
}
}
my frontend javascript code
function initialize() {
var itemId=$("#itemId").text();
const eventSource = new EventSource('/api/v1/item/subscription?id='+itemId);
eventSource.onmessage = e => {
const msg = e.data;
$("#price").text(msg);
Notification.requestPermission(function () {
if (Notification.permission === 'granted') {
// user approved.
var text = msg;
var notification = new Notification('Notification Alert!', {body: text});
setTimeout(notification.close(), 6 * 1000) // close in 5 sec
} else if (Notification.permission === 'denied') {
// user denied.
} else { // Notification.permission === 'default'
// user didn’t make a decision.
// You can’t send notifications until they grant permission.
}
});
};
eventSource.onopen = e => console.log('open');
eventSource.onerror = e => {
if (e.readyState == EventSource.CLOSED) {
console.log('close');
} else {
console.log(e);
}
};
eventSource.addEventListener('second', function (e) {
console.log('second', e.data);
}, false);
}
window.onload = initialize();
first request send request successfully
When timeout happen subscription request automatic generate which throwing 503 error
I have no idea why am getting 503 on subscribe again after timeout. Please help me to solve this problem.

I fixed that problem by moving GetData(message) method from controller class to service class with implementation now it working fine.
This is best way to stay with apache tomcat while making reactive programming in springboot. Or you can go with spring reactive(Webflux) which run at netty server.

Related

How to send unread messages notifications with Server Sent Events in Spring Boot?

I'm new to Spring Boot and web applications. I have to send notifications of unhandled/unread messages from a Spring Boot backend to a web client. I decided to use Server Sent Events since I think I don't need a bidirectional connection (otherwise I'd have thought of WebSockets).
I made a very simple REST controller which finds all unhandled messages in a db and sends them to the client. The problem is that it keeps sending forever all the messages, while I'd like to send a message only when it is added to the db, or when the client first connects to the server.
The behaviour I'd like to achieve is similar to a mail client or a messaging app, in which the user is notifyed not only on new messages but also of previous ones if he/she didn't mark them as read. The notification should happen only once when the client connects, not loop forever.
Here is my code:
#RestController
#CrossOrigin(origins = "*")
public class SseEmitterController {
private MessageDAO messageDAO;
private ExecutorService nonBlockingService = Executors
.newCachedThreadPool();
#Autowired
public SseEmitterController(MessageDAO messageDAO) {
this.messageDAO = messageDAO;
}
#GetMapping("/incoming_messages")
public SseEmitter handleSse() {
SseEmitter emitter = new SseEmitter();
nonBlockingService.execute(() -> {
try {
List<Message> messages = messageDAO.findByHandledFalse();
for (Message message: messages) {
emitter.send(message, MediaType.APPLICATION_JSON);
}
emitter.complete();
} catch (Exception ex) {
emitter.completeWithError(ex);
}
});
return emitter;
}
}
I know that the problem is caused by the fact that I query the db inside handleSse method, but I couldn't figure out how to do it outside.
Could you please help me?
Update October 05, 2021
I found out how to solve the problem, I didn't update the question because I didn't have the time, but since someone asked me to do so in the comments, I'm gonna explain my solution, hoping it may be helpful.
Here's my code:
The SseEmitterController is responsible for invoking the SseService on frontend's request:
#RestController
#CrossOrigin(origins = "*")
public class SseEmitterController {
private final SseService sseService;
#Autowired
SseEmitterController(SseService sseService) {
this.sseService = sseService;
}
#GetMapping("/incoming_messages")
public ResponseEntity<SseEmitter> handleSse() {
final SseEmitter emitter = new SseEmitter();
sseService.addEmitter(emitter);
emitter.onCompletion(() -> sseService.removeEmitter(emitter));
emitter.onTimeout(() -> sseService.removeEmitter(emitter));
return new ResponseEntity<>(emitter, HttpStatus.OK);
}
}
The SseService is called on a new message arrival (from another part of the application) and sends the notification (actually a server sent event) to the frontend (which previously called the endpoint in the controller above.
The service is called like so: sseService.sendHelpRequestNotification(helpRequest);
#Service
public class SseService {
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
public void addEmitter(final SseEmitter emitter) {
emitters.add(emitter);
}
public void removeEmitter(final SseEmitter emitter) {
emitters.remove(emitter);
}
public void sendMessagesNotification(Message message) {
List<SseEmitter> sseEmitterListToRemove = new ArrayList<>();
emitters.forEach((SseEmitter emitter) -> {
try {
emitter.send(message, MediaType.APPLICATION_JSON);
} catch (Exception e) {
sseEmitterListToRemove.add(emitter);
}
});
emitters.removeAll(sseEmitterListToRemove);
}
}
And finally there is another controller to get all previous unhandled messages (not involving server sent events):
#GetMapping(value = "/unhandled_help_requests")
public ResponseEntity<List<HelpRequest>> getUnhandledMessages() {
List<Message> resultSet = messageDAO.findByHandledFalse(Sort.by("date").and(Sort.by("time")));
return new ResponseEntity<>(resultSet, HttpStatus.OK);
}
So, to sum it up: the frontend calls the SseEmitterController to listen for new SSEs. These SSEs are created and sent whenever a new message arrives to the backend, via the SseService. Finally, to get all unhandled (for whatever reason) messages, there is a specific old fashioned controller.

why my http request work from postman and not from my angular application

i have this function in my SpringBoot applican's controller
#GetMapping("/cancel/{orderID}")
#ResponseStatus(HttpStatus.OK)
public void cancelOrder(#PathVariable long orderID)
{
Order order = this.orderRepository.findOrderById(orderID);
System.out.println(order);
order.setStatus(OrderStatus.CANCELLED.name());
this.orderService.cancel( order);
}
i'm trying to access it from my angular application but it doesn't work , i tried it with postman and it works just fine
cancelOrder(orderId: Number) {
return this.http.get(`${this.host}/orders/cancel/${orderId}`);
}
ngOnInit(): void {
this.setCancelStatus();
}
setCancelStatus () {
this.orderID = parseInt(this.order.id);
this.ecommerceService.cancelOrder(this.orderID);
console.log(this.orderID);
}
So this is the thing with Observables. You have to subscribe to them in order for the HTTP request to take flight.
ngOnInit(): void {
this.setCancelStatus();
}
setCancelStatus () {
this.orderID = parseInt(this.order.id);
// add subscribe here.
this.ecommerceService.cancelOrder(this.orderID).subscribe(response => {
console.log(response);
});
console.log(this.orderID);
}

Why does Gateway with void return is making async flow but it does sync when it return value? Spring Integration

I am new with Spring Integration. I was making some tests I realized the behavior of my app changes when the Gateway return void or return String. I'm trying to process the flow in the background (async) meanwhile I return a http message. So I did a async pipeline
#Bean
MessageChannel asyncChannel() {
return new QueueChannel(1);
}
#Bean
public MessageChannel asyncChannel2() {
return new QueueChannel(1);
}
#Bean
public MessageChannel asyncChannel3() {
return new QueueChannel(1);
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
PollerMetadata customPoller() {
PeriodicTrigger periodicTrigger = new PeriodicTrigger(2000, TimeUnit.MICROSECONDS);
periodicTrigger.setFixedRate(true);
periodicTrigger.setInitialDelay(1000);
PollerMetadata poller = new PollerMetadata();
poller.setMaxMessagesPerPoll(500);
poller.setTrigger(periodicTrigger);
return poller;
}
3 Activators
#ServiceActivator(inputChannel = "asyncChannel", outputChannel = "asyncChannel2")
public String async(String message) {
try {
Thread.sleep(5000);
log.info("Activator 1 " + message);
return message;
} catch (InterruptedException e) {
log.error("I don't want to sleep now");
}
return "";
}
#ServiceActivator(inputChannel = "asyncChannel2", outputChannel = "asyncChannel3")
public String async(String message){
log.info("Activator 2 "+ message);
try {
Thread.sleep(2000);
return message;
} catch (InterruptedException e) {
log.error("I don't want to sleep");
}
return "";
}
#ServiceActivator(inputChannel = "asyncChannel3")
public String result(String message) throws InterruptedException {
Thread.sleep(2000);
log.info("Activator 3 " + message);
return message;
}
I receive a message from Controller class
private final ReturningGateway returningGateway;
#PostMapping("/example")
public ResponseEntity post() {
returningGateway.processWhileResponse("Message example");
return ResponseEntity.ok(Map.of("Message","Http Done. Check the logs"));
}
The gateway
#Gateway(requestChannel = "asyncChannel")
public void processWhileResponse(String message_example);
The curious thing is when the gateway returns a void it making the process async so I can see the http message "Http Done. Check the logs" first, then I go to the logs and I see the async execution. but when the gateway returns a String I see the logs first and then the http message.
So I need the gateway returns a value but it keep the async way so I can get a http message
could you give a hand?
Sorry if I'm not using the right term. Thanks
So I need the gateway returns a value but it keep the async way so I can get a http message.
As long as you return some non-async type, it is going to block your code on the gateway call and wait for that return value to come back. Even if your flow behind that gateway is async, it still waits for a reply on the CountDownLatch barrier for replyChannel. In case of void return type there is no reply expectations and gateway exists immediately after sending a request message.
You may consider to have a Future as return type, but it still not clear when you would like to get the value: before returning from your controller method, or it is OK already after.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#async-gateway

Angular Post request goes to pending status and does not hit the server

I am trying to get the user list for a email Id using a post request , Sometimes the post request goes to pending status and it stays in the same status forever.If I restart the tomcat then the request works but after some times the same issue occurs.
Below is the post method calling in angular
loadBOEDetailsListByEmail(emailId, password) {
const url = `${environment.url}backofficeemployee/detailsByBoeEmailId/`;
const params = {
resetEmail: emailId
};
const myHeader = new HttpHeaders();
myHeader.append('Content-Type', 'application/x-www-form-urlencoded');
return Observable.create(observer => {
this.http.post(url, params, { headers: myHeader }).subscribe(
(response: any) => {
if (response && response.data && response.data[0] !== undefined) {
this.accountList = response.data;
const boe = response.data[0];
this.checkAccountValidity(boe, password, observer);
} else {
observer.error('No account found');
}
},
() => observer.error('Employee service call failed')
);
});
}
In the java side this is how I am receiving
// Backoffice employee - Details by BoeUserId
#PostMapping(value = "/detailsByBoeEmailId/")
public #ResponseBody Map<String, Object> getBackofficeemployeeDetailsByBoeEmailId(#RequestBody ResetpasswordRequestDto requestDto) {
log.info(" boeEmailId in getBackofficeemployeeDetails {}", requestDto.getResetEmail());
try {
List<BackofficeemployeeResponseDto> backofficeemployeeResponseDto = backofficeemployeeService
.getByBoeEmailId(requestDto.getResetEmail());
return JsonUtils.mapOK(backofficeemployeeResponseDto);
} catch (Exception e) {
log.error("Exception in getBackofficeemployeeDetailsByBoeEmailId", e);
return JsonUtils.mapError(ERROR_MSG + e.getMessage());
}
}

How to continue request processing after sending response from filter in Jersey?

I have a situation where I need to return an "accepted" response for every request received and publish the actual response later to a separate endpoint outside the service.
To implement the 'accepted' Response I implemented a filter.
public class AcknowledgementFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.abortWith(Response.accepted().build());
// call Resource method in new Thread() . <------ ?
}
}
Implementation of service endpoints:
#Path("/vendor")
public class VendorHandler {
#POST
public void addVendor(VendorRequest addVendorRequest)){
vendor = new Vendor();
Client.publish(vendor); // publish request to an endpoint
return null;
}
How do I call the addVendor of VendorHandler(or any method depends on request) from the acknowledgement filter?
Is there any other way to implement an accepted response for every request then process the request separately?
You can use AsyncResponse,
#GET
#ManagedAsync
#Produces(MediaType.APPLICATION_JSON)
public void getLives(#Suspended final AsyncResponse asyncResponse,
#DefaultValue("0") #QueryParam("newestid") final int newestId,
#QueryParam("oldestid") final Integer oldestId) {
asyncResponse.setTimeoutHandler(asyncResponse1 -> {
logger.info("reached timeout");
asyncResponse1.resume(Response.ok().build());
});
asyncResponse.setTimeout(5, TimeUnit.MINUTES);
try {
List<Life> lives = oldestId == null ?
Lifes.getLastLives(newestId) : Lifes.getOlderLives(oldestId);
if (lives.size() > 0) {
final GenericEntity<List<Life>> entity = new GenericEntity<List<Life>>(lives) {
};
asyncResponse.resume(entity);
} else LifeProvider.suspend(asyncResponse);
} catch (SQLException e) {
logger.error(e, e);
asyncResponse.resume(new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
}
}
Check this Link for more details.

Categories