Spring Boot Health Check - SQS Consumer - java

Is there a Spring Boot Actuator Health Check endpoint for SQS? I have built a SQS consumer and I want to check if SQS is up and running. I am not using JMSlistener for connecting to SQS but rather using Spring Cloud Libraries.
I implemented the below health check endpoint. This returns the below error when I delete the queue and try to hit the health check endpoint. If there is a connectivity issue or if the SQS service goes down , will I be getting a similar error which will eventually cause the health check endpoint to fail?
com.amazonaws.services.sqs.model.QueueDoesNotExistException: The
specified queue does not exist for this wsdl version. (Service:
AmazonSQS; Status Code: 400; Error Code:
AWS.SimpleQueueService.NonExistentQueue; Request ID:
cd8e205d-dc43-535e-931f-7332733bd16c)
public class SqsQueueHealthIndicator extends AbstractHealthIndicator {
private final AmazonSQSAsync amazonSQSAsync;
private final String queueName;
public SqsQueueHealthIndicator(AmazonSQSAsync amazonSQSAsync, String queueName) {
this.amazonSQSAsync = amazonSQSAsync;
this.queueName = queueName;
}
#Override
protected void doHealthCheck(Health.Builder builder) {
try {
amazonSQSAsync.getQueueUrl(queueName);
builder.up();
} catch (QueueDoesNotExistException e) {
e.printStackTrace();
builder.down(e);
}
}
}
Beans
#Bean
SqsQueueHealthIndicator queueHealthIndicator(#Autowired AmazonSQSAsync amazonSQSAsync, #Value("${sqs.queueName}") String queueName) {
return new SqsQueueHealthIndicator(amazonSQSAsync, queueName);
}
#Bean
SqsQueueHealthIndicator deadLetterQueueHealthIndicator(#Autowired AmazonSQSAsync amazonSQSAsync, #Value("${sqs.dlQueueName}") String deadLetterQueueName) {
return new SqsQueueHealthIndicator(amazonSQSAsync, deadLetterQueueName);
}

You have to write a custom health check like below to check your queue exists or not by calling getQueueUrl using AWS Java SDK lib.
#Component
public class SQSHealthCheck implements HealthIndicator {
#Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down()
.withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
public int check() {
/**
your logic to check queue exists or not using by calling getQueueUrl . e.g you will get queue url of a queue named "SampleQueue" like https://sqs.us-east-1.amazonaws.com/12XXX56789XXXX/SampleQueue
**/
return 0; // 0 or 1 based on result
}
}

Related

Sending message to a SQS queue fails from local in java

I'm not able to send message to sqs-queue from my java application.
I am running roribio16/alpine-sqs docker image for SQS in my local and I've created a standard queue.
Code snippets:
#Data
#Configuration
#EnableSqs
public class SQSConfig {
private final String amazonAWSAccessKey;
private final String amazonAWSSecretKey;
private final String amazonSQSRegion;
public SQSConfig(#Value("${aws.accesskey}") String amazonAWSAccessKey,
#Value("${aws.secretkey}") String amazonAWSSecretKey,
#Value("${sqs.aws.region}") String amazonSQSRegion) {
this.amazonAWSAccessKey = amazonAWSAccessKey;
this.amazonAWSSecretKey = amazonAWSSecretKey;
this.amazonSQSRegion = amazonSQSRegion;
}
#Bean
public QueueMessagingTemplate queueMessagingTemplate() {
return new QueueMessagingTemplate(amazonSQSClient());
}
#Bean
#Primary
public AmazonSQSAsync amazonSQSClient() {
return AmazonSQSAsyncClientBuilder.standard()
.withRegion(amazonSQSRegion)
.withCredentials(new AWSStaticCredentialsProvider(new DefaultAWSCredentialsProviderChain().getCredentials()))
.build();
}
private AWSStaticCredentialsProvider amazonAWSCredentials() {
return new AWSStaticCredentialsProvider(new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey));
}
}
MessageProducerService:
#Component
public class SQSMessageProducerService {
#Value("${sqs.endpoint}")
private String amazonSQSEndpoint;
#Autowired
private QueueMessagingTemplate queueMessagingTemplate;
public void sendMessage(String messageBody) {
Message messageBuilt = MessageBuilder.withPayload(messageBody).build();
try {
queueMessagingTemplate.send(amazonSQSEndpoint, messageBuilt);
} catch (MessagingException e) {
System.out.println(e);
}
System.out.println("message sent: " + messageBody);
}
}
Calling using -
sqsMessageProducerService.sendMessage(userEvent.toString());
Error stack -
InvalidAction; see the SQS docs. (Service: AmazonSQS; Status Code:
400; Error Code: InvalidAction; Request ID:
00000000-0000-0000-0000-000000000000)
Vars from properties file -
sqs.endpoint=${SQS_ENDPOINT:http://localhost:9324/test-sns-queue}
sqs.aws.region=${SQS_AWS_REGION:us-west-2}
Also, SQS instance is up in my local and can be accessed using http://localhost:9325/.
Is there some config that I'm missing here?
I think the problem might be that the endpoint is http://localhost:9324/queue/test-sns-queue

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 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

Java Websocket / MessageHandler return to global scope?

I'm facing the following problem and I found no working solution yet.
I have 3 different applications that should communicate with each other:
the UI part (1)
the backend application (2)
the microservice "in the cloud" (3)
The backend application provides a Webservice (REST) for the UI to get and put information from/to the microservice.
Everything I want to grab from the microservice works fine, but:
If I want to put data to the microservice, the specs require a websocket connection. This works fine too, but the microservice returns a message after the (un-)successful command, like
{"statusCode":200,"messageId":"1234567890"}
The problem now is: How can I grab this message in my application and send it back to the UI, so the user knows if the command was successful?
For the moment I tried this:
WebSocketClient.java
#OnMessage
public void onMessage(Session session, String msg) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(msg);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public static interface MessageHandler {
public String handleMessage(String message);
}
MyTotalAwesomeController.java
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
...
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
...
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
return message;
}
});
return ResponseEntity.ok("worked");
}
I can see the console output from the MessageHandler return, but I don't know how I can pass this to the parent method for return insted of just returning the ResponseEntity.ok().
I'm not very used to WebSocket connections in Java yet, so please don't judge me ;-)
Thank you for your help.
The code below will work under the assumption that the #OnMessage method is executed in a thread managed by the WebSocket client runtime. Please inspect the thread that runs the #OnMessage method.
If the above premise is true, the putDataToMicroservice() method, executed by a thread in the global scope, will wait until the WebSocket response arrives at the WS client thread, which will repass the message to the global scope thread. Then the execution in your controller class will continue.
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
// Queue for communication between threads.
private BlockingQueue<String> queue;
#PostConstruct
void init() {
queue = new SynchronousQueue<>(true);
// This callback will be invoked by the WebSocket thread.
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
#Override
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
// Pass message to the controller thread.
queue.put(message);
// Note that the return value is not necessary.
// You can take it out of the interface as well.
return null;
}
});
}
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
// At this point you make a WebSocket request, is that right?
doWebSocketRequest();
// This poll call will block the current thread
// until the WebSocket server responds,
// or gives up waiting after the specified timeout.
//
// When the WebSocket server delivers a response,
// the WS client implementation will execute the
// #OnMessage annotated method in a thread
// managed by the WS client itself.
//
// The #OnMessage method will pass the message
// to this thread in the queue below.
String message = queue.poll(30, TimeUnit.SECONDS);
if (message == null) {
// WebSocket timeout.
}
return ResponseEntity.ok("worked");
}
}

Akka Camel websocket client producer

I am trying to communicate with a websocket server using Apache Camel AHC-Websocket Component with Akka Camel in Java. In this case, websocket endpoint is goint to be a well known websocket public service.
Using:
JDK 8.x
Akka Java API with Akka Camel, Akka Actor and Akka SLF4J 2.3.9
Apache Camel with AHC WS Component 2.14.1
I followed Akka Camel tutorial for Java located over here.
Short description: Every response received by UntypedProducerActor when I make a request returns CamelMessage with body field as null. But when I make request via ProducerTemplate I receive correct response.
Long description: I am getting strange behaviour from Akka Camel when I make a request and expect a response from the websocket endpoint. When I make a request to the endpoint via defined ActorRef, for example like so:
ActorRef wsProducer = getContext().actorOf(SimpleProducer.props("ahc-ws:echo.websocket.org"));
final Timeout timeout = new Timeout(3, TimeUnit.MINUTES);
final Future<Object> future = Patterns.ask(wsProducer, "Please, respond!", timeout);
final Object result = Await.result(future, timeout.duration());
I clearly see in the logs that Apache Camel websocket endpoint received a response:
DEBUG o.a.c.component.ahc.ws.WsEndpoint - received message --> Please, respond!
But the result object will be a CamelMessage with body field always set to null. The same CamelMessage present in public Object onTransformResponse(Object message) method of my SimpleProducer.
However, when I make request via ProducerTemplate like this:
final Camel camel = CamelExtension.get(getContext().system());
final CamelContext context = camel.context();
final ProducerTemplate template = camel.template();
Object result = template.requestBody("ahc-ws:echo.websocket.org", "Alpha is there!");
It works and result will contain correct response body: "Alpha is there!".
My SimpleProducer is pretty much the same as in tutorial:
public class SimpleProducer extends UntypedProducerActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private final String mEndpointUri;
public SimpleProducer(final String serverUrl) {
mEndpointUri = serverUrl;
}
#Override
public String getEndpointUri() {
return mEndpointUri;
}
#Override
public Object onTransformResponse(Object message) {
return super.onTransformOutgoingMessage(message);
}
#Override
public boolean isOneway() {
return false;
}
public static Props props(final String endpointUri) {
return Props.create(new Creator<SimpleProducer>() {
private static final long serialVersionUID = 1L;
#Override
public SimpleProducer create() throws Exception {
return new SimpleProducer(endpointUri);
}
});
}
}
Maybe somebody had the same issue and can help me out?

Categories