My goal is to send one request from frontend to backend and receive multiple responses. I'm using WebSocket because responses are very frequent and WebSocket seems to be best protocol to do it and SseEmitter send multiple responses from backend.
Here is my request controller:
#MessageMapping("/emitter")
#SendTo("/topic/response")
public SseEmitter output(RunData runData) throws Exception {
SseEmitter emitter = new SseEmitter();
new Thread(new Runnable() {
#Override
public void run() {
try {
RemoteHostController rhc = new RemoteHostController(runData);
rhc.execute();
while (rhc.getActiveCount() > 0) {
emitter.send(rhc.getAllOutput());
Thread.sleep(2000);
}
emitter.complete();
} catch (Exception ee) {
ee.printStackTrace();
emitter.completeWithError(ee);
}
}
}).start();
return emitter;
}
RemoteHostController is managing connections and getAllOutput returns output from the hosts.
Frontend application is running quite simple index.html that is connecting to websocket using Stomp and SockJS, sends data to server and generate tag with data from response:
function connect() {
var socket = new SockJS('http://localhost:8080/emitter');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/response', function(greeting){
showOutput(greeting.body);
});
});
}
function sendData() {
var hostname = document.getElementById('hostname').value;
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
var command = document.getElementById('command').value;
stompClient.send("/app/emitter", {}, JSON.stringify({ 'hostname': hostname,
'username': username,
'password': password,
'command': command}));
}
function showOutput(message) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
When I send data to backend only response I get is:
{"timeout":null}
It's SseEmitter timeout field, when I change timeout it will return {"timeout":<timeout_value>}.
I can see in logs that RemoteHostController is connecting to hosts and executing commands properly.
Am I doing something wrong? Or WebSocket only supports one request one response communication?
Here is an example of both WebSocket and SSE. As noted above SSE is not supported by IE browsers. Adding as much as I can for completeness. Make sure you are not using a RestController when you use SeeEmitter because that will return the object and that is my guess from the description above.
pom.xml
<dependencies>
<!-- Spring boot framework -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
Web socket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
super.configureMessageBroker(registry);
registry.enableSimpleBroker("/topic");
}
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/socketrequest").withSockJS();
}
}
Request Data:
public class RequestData {
private String string1;
private String string2;
// excluding getters and setters
}
Web socket controller:
#Controller
public class WebSocketController {
#Autowired
SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/processrequest")
void runWebSocket( RequestData requestData ) {
new Thread(new RunProcess(requestData)).start();
}
private class RunProcess implements Runnable {
private RequestData requestData;
RunProcess(RequestData requestData) {
this.requestData = requestData;
}
public void run() {
simpMessagingTemplate.convertAndSend("/topic/response", requestData.getString1());
simpMessagingTemplate.convertAndSend("/topic/response", requestData.getString2());
simpMessagingTemplate.convertAndSend("/topic/response", "A third response via websocket");
}
}
}
Sse Controller:
#Controller
public class SseController {
#RequestMapping("/emitter")
public SseEmitter runEmitter(#RequestParam(value = "string1") String string1,
#RequestParam(value = "string2") String string2)
{
SseEmitter sseEmitter = new SseEmitter();
RequestData requestData = new RequestData();
requestData.setString1(string1);
requestData.setString2(string2);
new Thread(new RunProcess(requestData,sseEmitter)).start();
return sseEmitter;
}
private class RunProcess implements Runnable {
private RequestData requestData;
private SseEmitter sseEmitter;
RunProcess(RequestData requestData, SseEmitter sseEmitter) {
this.requestData = requestData;
this.sseEmitter = sseEmitter;
}
public void run() {
try {
sseEmitter.send(requestData.getString1());
sseEmitter.send(requestData.getString2());
sseEmitter.send("A third response from SseEmitter");
sseEmitter.complete();
} catch (IOException e) {
e.printStackTrace();
sseEmitter.completeWithError(e);
}
}
}
}
Html code:
<script src="/javascript/sockjs-0.3.4.js"></script>
<script src="/javascript/stomp.js"></script>
<script type="text/javascript">
var stompClient = null;
function connect() {
var socket = new SockJS('http://localhost:8085/socketrequest');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/response', function(message){
showOutput(message.body);
});
});
}
function doWebsocket() {
stompClient.send("/processrequest", {}, JSON.stringify({ 'string1': 'The first string', 'string2' : 'The second string' }));
}
function doSse() {
console.log("doSse");
var rtUrl= '/emitter?string1=first string sse&string2=second string sse';
var source = new EventSource(rtUrl);
source.onmessage=function(event){
showOutput(event.data)
};
}
function showOutput(message) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
connect();
</script>
</head>
<div>
Starting page
</div>
<div>
<button id="websocket" onclick="doWebsocket();">WebSocket</button>
<button id="sse" onclick="doSse();">Server Side Events</button>
</div>
<div >
Response:
<p id="response"></p>
</div>
</html>
Related
I'm new in Spring and I trying log in to chat application with spring security username.
But controller or stomp don't recognize input. And don't allows to enter chat
Where is I should to write in stomp or controller?
Thank you.
Chat Controller
#Controller
public class ChatController {
#GetMapping("/chat")
public String chat() {
return "chat";
}
#MessageMapping("/chat.sendMessage")
#SendTo("/topic/public")
public ChatMessagePojo sendMessage(#Payload ChatMessagePojo chatMessagePojo) {
return chatMessagePojo;
}
#MessageMapping("/chat.addUser")
#SendTo("/topic/public")
public ChatMessagePojo addUser(#Payload ChatMessagePojo chatMessagePojo, SimpMessageHeaderAccessor headerAccessor) {
// Add username in web socket session
headerAccessor.getSessionAttributes().put("email", chatMessagePojo.getSender());
return chatMessagePojo;
}
ChatMessage class
public class ChatMessagePojo {
private MessageType type;
private String content;
private String sender;
public enum MessageType {
CHAT,
JOIN,
LEAVE
}
}
Stomp
const connect = () => {
const Stomp = require("stompjs");
var SockJS = require("sockjs-client");
SockJS = new SockJS("http://localhost:8080/ws");
stompClient = Stomp.over(SockJS);
stompClient.connect({}, onConnected, onError);
};
const onConnected = () => {
console.log("connected");
stompClient.subscribe(
"/user/" + currentUser.id + "/queue/messages",
onMessageReceived
);
};
const sendMessage = (msg) => {
if (msg.trim() !== "") {
const message = {
senderId: currentUser.id,
recipientId: activeContact.id,
senderName: currentUser.name,
recipientName: activeContact.name,
content: msg,
timestamp: new Date(),
};
stompClient.send("/app/chat", {}, JSON.stringify(message));
}
};
Example login screen to chat:
I tried but failed, and then I solved it with my own solution. Maybe you can refer to it.
Make a socket endpoint for authentication and send the ID and password for authentication.
Save id and session after successful authentication.
Then get the user id through the session map SESSION_USERID.
Frontend
stompClient.send("/app/chat/login", {}, "{type: AUTH, data: {id: 'mock', pwd: '12345678'}}");
Backend
public static final Map<String, Long> SESSION_USERID = new ConcurrentHashMap<>();
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
// parse message to object
if (message.type == AUTH) {
// do auth
// if auth succeed, cache the user
SESSION_USERID.put(userId, session.getId());
} else if (message.type == CHAT) {
Long userId = SESSION_USERID.get(session.getId());
if (userId == null) {
// Send error or close session
} else {
// Send messages to other peoples
}
}
}
//----
Scenario:
Client(angular js 1.4) will call Rest endpoint to get data, the server(spring boot) will process the list of files and will return accurate data. To process the list of files, the server will take time depending on the number of files. so I have implements STOMP notification as to send a notification to the client saying "List of files have been processed and here is the bunch of files(result)".
Issue:
Stomp connection is established successfully and client also gets subscribed, but when the server publishes the events, client is not able to receive.
Below is my code snippet:
WebSocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic"); // Enables a simple in-memory broker
}
}
SocketController.java
#Controller
public class SocketController {
#SendTo("/topic/public")
public String sendMessage() {
LOGGER.info("====> chatMessage()");
return "List updated successfully";
}
}
main.js
connect() {
var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);
event.preventDefault();
console.log("socket: ", socket);
console.log("stompClient: ", stompClient);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/public', function (payload) {
console.log("payload: ", payload);
var message = JSON.parse(payload.body);
console.log("message: ", message);
});
}, function (error) {
console.log("onError() called");
console.log("error: ",error);
});
};
Scripts used:
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
connect() method of main.js is called on button click. Probably there is an issue regarding scope I guess.
Use SimpMessagingTemplate to convert and send messages to the specific topic.
#Controller
public class SocketController {
private static final Logger LOGGER = LoggerFactory.getLogger(SocketController.class);
#Autowired private SimpMessagingTemplate template;
public void sendMessage(String message) {
LOGGER.info("====> sendMessage:");
this.template.convertAndSend("/topic/public", message);
}
}
I tried to implement this example https://spring.io/guides/gs/messaging-stomp-websocket/ and it all worked fine. So i moved forward and tried to make it work with a standalone HornetQ.
So, i defined a topic in HornetQ config - /topic/requests
Here are the changes i've made
In index.html i got rid of sockJS
function connect() {
var ws = 'ws://127.0.0.1:61613/stomp';
stompClient = Stomp.client(ws);
stompClient.connect("guest", "guest", function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('jms.topic.requests', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
});
}
Sending message from browser
function sendName() {
var name = document.getElementById('name').value;
stompClient.send("jms.topic.requests", {}, JSON.stringify({ 'name': name }));
}
ShowGreeting
function showGreeting(message) {
var response = document.getElementById('response');
console.log('response: ' + response);
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
Configured Spring to work with a standalone broker
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue");
//registry.setApplicationDestinationPrefixes("/jms");
}
Controller
#MessageMapping("/stomp")
#SendTo("/topic/requests")
public Greeting greeting(HelloMessage message) throws Exception {
System.out.println("Controller called!");
Thread.sleep(3000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
As a result, it can connect to a running instance of hornetQ and send messages to a topic. However, instead of printing specified string back it just prints "undefined". I know that message is reaching the queue, as i have another browser-based subscriber.
That println in controller is never called, so i suspect i've failed to properly configure it, but i have no idea what would it be.
Greeting
public class Greeting {
private String content;
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
I want to send an alerts to client if anything goes wrong in server side coding using STOMP Client. I have searched in google and didn't find any examples but I did find STOMP client documentation.
I have written following code in controller
WebSocketClient transport = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(transport);
stompClient.setMessageConverter(new StringMessageConverter());
String url = "ws://localhost:8080/SpringMVCStompClient/alert";
StompSessionHandler handler = new MySessionHandler();
stompClient.connect(url, handler);
and MyHandler code is:
public class MySessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
Alert alert= new Alert();
alert.setName("Server");
alert.setMessage("Message from server");
session.send("/topic/alert", alert);
}
}
when i am trying to execute this execution is not going to MyHandler class. Is this is the right way to do?. Help he me how can i send a alert to client.
Websocket XML Configuration
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/alert">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
Controller
#Autowired
public SpringMVCController(SimpMessagingTemplate template) {
this.template = template;
}
#Autowired
private SimpMessagingTemplate template;
#MessageMapping("/alert")
#SendTo("/topic/alert")
public Alerts alerts(Alerts alerts) throws Exception {
Alert alert= new Alert();
alert.setName("Server");
alert.setMessage("Message from server");
this.template.convertAndSend("/topic/alert", alert);
return alerts;
}
Client js code service
(function(angular, SockJS, Stomp) {
angular.module("alertApp.services").service("AlertService", function($q, $timeout) {
var service = {}, listener = $q.defer(), socket = {
client: null,
stomp: null
};
service.SOCKET_URL = "/SpringMVCStompClient/alert";
service.ALERT_TOPIC = "/topic/alert";
service.ALERT_BROKER = "/app/alert";
service.receive = function() {
return listener.promise;
};
service.send = function(alert) {
socket.stomp.send(service.ALERT_BROKER, { priority: 9}, JSON.stringify({
message: alert.message,
name: alert.name
}));
};
service.disconnect = function() {
socket.stomp.disconnect();
};
var startListener = function() {
socket.stomp.subscribe(service.ALERT_TOPIC, function(data) {
listener.notify(JSON.parse(data.body));
});
};
service.initialize = function() {
socket.client = new SockJS(service.SOCKET_URL);
socket.stomp = Stomp.over(socket.client);
socket.stomp.connect({}, startListener);
};
return service;
});
})(angular, SockJS, Stomp);
I'm trying to send a file using websocket connection to my server:
I've implemented the websocket server side using java Spring
the client side is in javaScript
for some reason each time i send a binary message from client side using "blob" or "arraybuffer. the server recognise the message as text message not as binary.
what am i missing here?
Client Side
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Chat</title>
</head>
<body>
<h2>File Upload</h2>
Select file
<input type="file" id="filename" />
<br><br><br>
<input type="button" value="Connect" onclick="WebSocketTest()" />
<br><br><br>
<input type="button" value="Upload" onclick="sendFile()" />
<script>
"use strict"
var ws;
function WebSocketTest()
{
if ("WebSocket" in window)
{
console.log("WebSocket is supported by your Browser!");
// Let us open a web socket
ws = new WebSocket("ws://xx.xx.xx.xx:yyyy/service/audioHandler");
ws.onopen = function()
{
// Web Socket is connected, send data using send()
ws.send(JSON.stringify({userName:'xxxx',password:'sgdfgdfgdfgdfgdf'}));
console.log("Message is sent...");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
console.log("Message is received...");
};
ws.onclose = function()
{
// websocket is closed.
console.log("Connection is closed...");
};
}
else
{
// The browser doesn't support WebSocket
console.log("WebSocket NOT supported by your Browser!");
}
}
function sendFile() {
var file = document.getElementById('filename').files[0];
ws.binaryType = "arraybuffer";
//ws.send('filename:'+file.name);
var reader = new FileReader();
var rawData = new ArrayBuffer();
console.log(file.name);
reader.loadend = function() {
}
reader.onload = function(e) {
rawData = e.target.result;
ws.send(rawData);
console.log("the File has been transferred.")
//ws.send('end');
}
reader.readAsArrayBuffer(file);
}
</script>
</body>
</html>
Server Side
public class WebSocketController extends BinaryWebSocketHandler {
#Autowired
private ApplicationContext applicationContext;
#Autowired
private CallScopeStore callScopeStore;
private static Logger logger = Logger.getLogger(AudioHandler.class);
private static final String STOP_MESSAGE = "stop";
#Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
try {
//do something....
} catch (Exception e) {
logger.error(e, e);
throw new RuntimeException(e);
}
}
#Override
protected void handleTextMessage(final WebSocketSession session, TextMessage message) {
try {
//do something....
}
} catch (Exception e) {
logger.error(e, e);
throw new RuntimeException(e);
}
}
}
You have to send the file as a blob.
ws = new WebSocket("ws://xx.xx.xx.xx:yyyy/service/audioHandler");
ws.binaryData = "blob";
ws.send(message); // Blob object
Probably you can find an error as you mention: "CloseStatus[code=1009, reason=No async message support and buffer too small. Buffer size: [8,192], Message size: [7,816,684]]". That happens because your WebSocket Engine need to be configured to allow binary message with the size you want. For example, in your WebSocketConfig:
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(500000);
container.setMaxBinaryMessageBufferSize(500000);
return container;
}
As you can see, you can set the maximun size allowed for your text and binary messages, just specify a size bigger than 7,816,684 (the file you was trying to send). By default, your buffer size is [8,192] so if you send a file with a smaller size than your buffer size, there shouldn't be problems. For more information, you can check websocket spring documentation.
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(500000);
container.setMaxBinaryMessageBufferSize(500000);
return container;
}
WebSocketMessageBroker invalid. #robert08
#Configuration
#EnableWebSocketMessageBroker
public class MyWebSocketMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").setAllowedOrigins("*");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setPathMatcher(new AntPathMatcher("."));
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration webSocketTransportRegistration) {
webSocketTransportRegistration
.setMessageSizeLimit(1024 * 1024)
.setSendBufferSizeLimit(1024 * 1024 );
}
}