I would like to subscribe and receive any events using objects instead of strings when using Spring Boot WebSockets. If my method returns a string and I use the StringMessageConverter my code successfully listens for the /topic/rooms/created event.
If I return a Room object instead and use MappingJackson2MessageConverter then my subscription no longer receives any messages.
#MessageMapping("/rooms/create/{roomName}")
#SendTo("/topic/rooms/created")
#CrossOrigin(origins = "http://localhost:3000")
public Room createRoom(#DestinationVariable final String roomName) {
return roomService.createRoom(roomService.getRooms().size(), roomName);
}
This sends the message and creates the room successfully. The response isn't picked up by the subscription.
final String roomName = "RoomName";
final StompSession.Subscription subscription = stompSession.subscribe("/topic/rooms/created", new StompFrameHandler() {
#Override
public Type getPayloadType(final StompHeaders headers) {
return Room.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
// Not called
System.out.println("Received message");
}
});
System.out.println("Sending message");
stompSession.send("/app/rooms/create/" + roomName, null);
I've also tried creating a Room instance and using a jackson object mapper to convert to JSON with no issues.
How can I resolve this?
Related
Reading message from Azure service bus:
org.springframework.messaging.converter.MessageConversionException: Cannot convert from [org.apache.qpid.jms.message.JmsMessage] to [java.lang.String] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#39945b52, failedMessage=org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#39945b52
java code:
import org.apache.qpid.jms.message.JmsMessage;
#Component
Public class AzureserviceBusListner {
private final static String QUEUE_NAME = "teste01";
#JmsListener(destination = QUEUE_NAME,containerFactory = "jmsListenerContainerFactory")
public void receive(String msg) {
System.out.println("Azure received message:::: "+msg);
}
}
You can send a JmsTextMessage iinstead of a JmsMessage:
//Autowire a JmsTemplate first, and then
jmsTemplate.send("<your-destination-name>", new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
TextMessagemessage = session.createTextMessage("message payload");
return message;
}
});
I am curious of what's is the need here to send a JmsMessage instead of sub class? I think you can also use message converter to convert the JmsMessage to sub classes as you need when sending/receiving messages.
Besides, do you use the azure-spring-boot-starter-servicebus-jms
If so, converting JmsMessage to other JAVA type in message converter will not work because that library doesn't support customize messageconverter for JmsListener.
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.
I have configured Pusher Server on ASP.net MVC website as:
[HttpPost]
public async Task<ActionResult> HelloWorld() {
var options = new PusherOptions
{
Cluster = "ap2",
Encrypted = true
};
var pusher = new Pusher(
"123",
"ABC",
"XYZ",
options);
var result = await pusher.TriggerAsync(
"my-channel",
"my-event",
new { message = "hello world" } );
return new HttpStatusCodeResult((int)HttpStatusCode.OK);
}
}
And I have made client in Java Swings as:
PusherOptions options = new PusherOptions();
options.setCluster("ap2");
Pusher pusher = new Pusher("XYZ", options);
Channel channel = pusher.subscribe("my-channel");
channel.bind("my-event", new SubscriptionEventListener() {
#Override
public void onEvent(String channelName, String eventName, final String data)
{
System.out.println(data);
}
});
pusher.connect();
This works fine and I receive the messages in Client JAVA app whenever I send any message from the Server.
But how can I send Messages from Client to the Server alongwith its ClientID?
There are different ways you can do this. If you have a token or cookie for your logged in users you can (and should) use those for sensitive data. Otherwise, to send data basically can be fitted in your http body and received by your endpoint as parameters or complex objects.
public async Task<ActionResult> HelloWorld(string foo, string bar) {
//Stuff goes here
}
Normally your content-type here is application/json but you can consume other types as well.
Then the body could simply look like:
{
"foo":"Hello",
"bar":"World"
}
I am working on spring web socket. i just create a controller for calling apis through WS. but unfortunately its mapping is not done. so i cant call that apis from client.
This is my controller class
#Controller
public class FloorController {
#MessageMapping("/floormapupdate")
public Message greeting(#Header(value = "nativeHeaders") Map s, Principal principal, Message message) throws Exception {
String type = (String) ((List) (s.get("type"))).get(0);
Map headers = new HashMap();
headers.put("type", type);
System.out.println("===================" + principal.getName());
simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/channel/me", message);
return message;
}
}
what is the issue with it ?
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");
}
}