I have implemented Web Socket using Spring MVC and it is working fine for me i.e work from one browser to another browser which is open for those socket using this code.
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public HelloMessage greeting(HelloMessage message) throws Exception {
Thread.sleep(3000); // simulated delay
return message;
}
Can any one help me for who to call #SendTo("/topic/greetings") from normal api controller.I have try using this but it is not working for me
#RequestMapping(value = "/sendMessage")
#SendTo("/topic/greetings")
public HelloMessage sendMessage() throws Exception {
return new HelloMessage((int) Math.random(), "This is Send From Server");
}
any idea for this?
Thanks
I have found solution for that
#Autowired
private SimpMessagingTemplate template;
#RequestMapping(value = "/sendMessage")
public void sendMessage() throws Exception {
this.template.convertAndSend("/topic/greetings", new HelloMessage(
(int) Math.random(), "This is Send From Server"));
}
by using this we can send message to open WebSocket.
Thanks
Related
Followed this tutorial regarding websockets (https://www.baeldung.com/websockets-spring) and this works locally on my computer.
My issue is that calling the method directly with an endpoint (/send) does not send a message to all subscribed users, only when stompclient talks to the #MessageMapping directly.
#MessageMapping("/liveoffers")
#SendTo("/topic/messages")
public OutputMessage send(final Message message) throws Exception {
final String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
#GetMapping(value = "/send")
#ResponseStatus(HttpStatus.OK)
public void invokeSend() throws Exception {
send(new Message("from", "text"));
}
Is there anyway I can call the #MessageMapping in a similar way below but actually have it functioning?
Have you considered using SimpMessagingTemplate to send a message instead of calling a mapped method?
#Autowired
SimpMessagingTemplate template;
#GetMapping(value = "/send")
#ResponseStatus(HttpStatus.OK)
public void invokeSend() throws Exception {
template.convertAndSend("/topic/messages", new Message("from", "text"));
}
I'm trying to send the UDP request and receive the response. Spring Integration has the appropriate instruments for such kind of task: UnicastSendingMessageHandler and UnicastReceivingChannelAdapter. I configured it in the following way
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "requestChannel")
public UnicastSendingMessageHandler unicastSendingMessageHandler() {
UnicastSendingMessageHandler unicastSendingMessageHandler = new UnicastSendingMessageHandler("239.255.255.250", 1982);
return unicastSendingMessageHandler;
}
#Bean
public UnicastReceivingChannelAdapter unicastReceivingChannelAdapter() {
UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(8080);
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
return unicastReceivingChannelAdapter;
}
How I send a message (I'm using sendDiscoveryMessage() wherever I want):
#Service
public class DiscoveryService {
private static final String DISCOVERY_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1982\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "ST: wifi_bulb";
private final MessageChannel requestChannel;
public DiscoveryService(final MessageChannel requestChannel) {
this.requestChannel = requestChannel;
}
public void sendDiscoveryMessage() {
requestChannel.send(new GenericMessage<>(DISCOVERY_MESSAGE));
}
}
At this point, I can check the packets via WireShark and ensure that Datagram was sent and the appropriate response was sent too.
The only question is how to receive this response. As far as I understand reading the documentation, I need the method annotated with #ServiceActivator. But I don't understand where (which channel) I should receive the response (in order to correctly specify #ServiceActivator(inputChannel="")). Also, I'm not sure about #ServiceActivator(inputChannel = "requestChannel") I put for UnicastSendingMessageHandler bean.
I tried to create the following method(assuming that the response will come to the same channel):
#ServiceActivator(inputChannel = "requestChannel")
public void receiveResponse(Message<String> response) {
System.out.println(response);
}
but it actually intercepts my own request message (seems logical to me, because I send the request to requestChannel).
So I don't understand how many channels I need (maybe I need 1 for request and 1 for response) and how to create #ServiceActivator to catch the response.
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
You are sending the result to nullChannel which is like /dev/null on Unix; you are discarding it.
Use #ServiceActivator(inputChannel = "replyChannel") and
unicastReceivingChannelAdapter.setOutputChannelName("replyChannel");
I'm trying to set up a Spring SseEmitter to send a sequence of updates of the status of a running job. It seems to be working but:
Whenever I call emitter.complete() in in my Java server code, the javascript EventSource client calls the registered onerror function and then calls my Java endpoint again with a new connection. This happens in both Firefox and Chrome.
I can probably send an explicit "end-of-data" message from Java and then detect that and call eventSource.close() on the client, but is there a better way?
What is the purpose of emitter.complete() in that case?
Also, if I always have to terminate the connection on the client end, then I guess every connection on the server side will be terminated by either a timeout or a write error, in which case I probably want to manually send back a heartbeat of some kind every few seconds?
It feels like I'm missing something if I'm having to do all this.
I have added the following to my Spring boot application to trigger the SSE connection close()
Server Side:
Create a simple controller which returns SseEmitter.
Wrap the backend logic in a single thread executor service.
Send your events to the SseEmitter.
On complete send an event of type complete via the SseEmitter.
#RestController
public class SearchController {
#Autowired
private SearchDelegate searchDelegate;
#GetMapping(value = "/{customerId}/search")
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Search Sources", notes = "Search Sources")
#ApiResponses(value = {
#ApiResponse(code = 201, message = "OK"),
#ApiResponse(code = 401, message = "Unauthorized")
})
#ResponseBody
public SseEmitter search(#ApiParam(name = "searchCriteria", value = "searchCriteria", required = true) #ModelAttribute #Valid final SearchCriteriaDto searchCriteriaDto) throws Exception {
return searchDelegate.route(searchCriteriaDto);
}
}
#Service
public class SearchDelegate {
public static final String SEARCH_EVENT_NAME = "SEARCH";
public static final String COMPLETE_EVENT_NAME = "COMPLETE";
public static final String COMPLETE_EVENT_DATA = "{\"name\": \"COMPLETED_STREAM\"}";
#Autowired
private SearchService searchService;
private ExecutorService executor = Executors.newCachedThreadPool();
public SseEmitter route(SearchCriteriaDto searchCriteriaDto) throws Exception {
SseEmitter emitter = new SseEmitter();
executor.execute(() -> {
try {
if(!searchCriteriaDto.getCustomerSources().isEmpty()) {
searchCriteriaDto.getCustomerSources().forEach(customerSource -> {
try {
SearchResponse searchResponse = searchService.search(searchCriteriaDto);
emitter.send(SseEmitter.event()
.id(customerSource.getSourceId())
.name(SEARCH_EVENT_NAME)
.data(searchResponse));
} catch (Exception e) {
log.error("Error while executing query for customer {} with source {}, Caused by {}",
customerId, source.getType(), e.getMessage());
}
});
}else {
log.debug("No available customerSources for the specified customer");
}
emitter.send(SseEmitter.event().
id(String.valueOf(System.currentTimeMillis()))
.name(COMPLETE_EVENT_NAME)
.data(COMPLETE_EVENT_DATA));
emitter.complete();
} catch (Exception ex) {
emitter.completeWithError(ex);
}
});
return emitter;
}
}
Client Side:
Since we specified the name of event on our SseEmitter, an event will be dispatched on the browser to the listener for the specified event name; the website source code should use addEventListener() to listen for named events. (Notice: The onmessage handler is called if no event name is specified for a message)
Call the EventSource on the COMPLETE event to release the client connection.
https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
var sse = new EventSource('http://localhost:8080/federation/api/customers/5d96348feb061d13f46aa6ce/search?nativeQuery=true&queryString=*&size=10&customerSources=1,2,3&start=0');
sse.addEventListener("SEARCH", function(evt) {
var data = JSON.parse(evt.data);
console.log(data);
});
sse.addEventListener("COMPLETE", function(evt) {
console.log(evt);
sse.close();
});
According to the HTML standard for Server-sent events
Clients will reconnect if the connection is closed; a client can be told to stop reconnecting using the HTTP 204 No Content response code.
So Spring's SseEmitter behaves as expected and the purpose of complete() is to make sure all the events were sent and then to close the connection.
You need to either implement server-side logic that would return 204 http code on subsequent requests (e.g. by checking session id) or to send a special event and close the connection from client side after receiving it as suggested by Ashraf Sarhan
I can't get the Spring Message Sample to work in Unit tests using the SimpMessagingTemplate to send messages to the Endpoints.
I followed the instructions here:
https://spring.io/guides/gs/messaging-stomp-websocket/
So far my Controller looks like:
#Data #NoArgsConstructor #AllArgsConstructor
public static class Message {
private Long id;
private String value;
private long time;
}
#MessageMapping("/message")
#SendTo("/topic/response")
public Message slowEndpont(Message message) throws Exception {
Thread.sleep(3000); // simulated delay
System.err.println("Message Received: " + message);
return new Message(message.id, "Hello Client", System.currentTimeMillis());
}
My Unit Test now tries to send a message:
#Autowired
SimpMessagingTemplate messageTemplate;
#Test
public void sendMessage() throws Exception {
System.err.println("** Sending messages...");
messageTemplate.convertAndSend("/app/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
messageTemplate.convertAndSend("/topic/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
messageTemplate.convertAndSend("/queue/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
messageTemplate.convertAndSend("/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
System.err.println("** Messages send!");
Thread.sleep(1500);
}
Full Code sample is here:
https://github.com/puel/training/tree/master/messaging
So far so good. Messages are all send. But never received. I traced it down and the registry of the MessageTemplate is empty. But why?
This problem seems to be close too:
Send Message to all clients via SimpMessagingTemplate in ServletContextListener
But using MessageSendingOperations doesn't help either.
Thanks,
Paul
I created a java project with glassfish and posted a simple REST GET service like this:
#Path("/get")
public class Rest {
#Path("test/{user}/")
#GET
public String getTest(#PathParam("user") String id) throws IOException {
//send message to websocket client and wait for response
//return "websocket client response";
}
}
this works fine.
I also have a websocket server implementation in the same project. This implementation allows me to send data to the connected clients.
This is my WebSocket implementation:
#ServerEndpoint("/websocket")
public class WebSocketServer {
#OnOpen
public void onOpen(Session userSession){
System.out.println("Se conecto un nuevo cliente");
Modelo.getInstance().users.add(userSession);
}
#OnMessage
public void onMessage(String message,Session userSession) throws IOException{
String username=(String) userSession.getUserProperties().get("username");
if(username==null){
userSession.getUserProperties().put("username", message);
userSession.getBasicRemote().sendText(Modelo.getInstance().buildJsonData("Servidor","nuevo cliente conectado como: "+message));
}else{
Iterator<Session> iterator=Modelo.getInstance().users.iterator();
while(iterator.hasNext()){
iterator.next().getBasicRemote().sendText(Modelo.getInstance().buildJsonData(username,message));
}
}
}
#OnClose
public void onClose(Session userSession){
Modelo.getInstance().users.remove(userSession);
}
#OnError
public void onError(Throwable t){
t.printStackTrace();
}
}
this works fine too.
When the REST method is called i can send successfully a message to one of my websockets clients.
The thing is that i want to return as the REST response, the data that the WebSocket client sends me.
So...
1)Receive REST GET request in Java Server
2)Send via websocket to the client i want to get the info from
3)Respond the REST GET request with the message the websocket client send me.
How can i accomplish this?
[SOLVED]?
I found a way to do this, please i would like to know what do you think.
I found this article: here about async rest reponses.
So i implemented, its the first thing come to my mind, i save the websocket client message in an array, and the REST request is responded when the array has a message.
#Path("/resource")
#GET
public void asyncGet(#Suspended final AsyncResponse asyncResponse) throws IOException {
Modelo.getInstance().enviarMensaje("5", "escenas");
new Thread(new Runnable() {
#Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
while(Modelo.getInstance().responses.size()==0){
}
String result=Modelo.getInstance().responses.get(0);
Modelo.getInstance().responses.clear();
return result;
// ... very expensive operation
}
}).start();
}
I know there a more things to validate this reponses, but at first it works.
I also edit the websockerserver.java to save in the array the response.
Thank you very much
REST works over HTTP which is a request/response model of communication. Which means you need to send a request in order to get a response. Web Sockets is a full duplex socket model. This means the client or the server can send a message as long as the connection is up. The challenge is you're trying to send a response with REST without a request. You could queue the response from the web socket and then send it back with the next REST response. This would however require the REST client to poll the server periodically since you would not have an indication of when the Web Socket client responded.