I'm trying to setup connection between Flutter and Spring boot as backend using websocket, but I came across many problems. I found in the Internet many tutorials even on StackOverflow but still I can't correctly build my chat application.
I have been using this tutorial for backend: https://kiberstender.github.io/miscelaneous-spring-websocket-stomp-specific-user/
I just want to simple print the log on my backend service after incoming message, but I don't know what I'm doing wrong.
Flutter code:
try {
StompClient stompClient = StompClient(
config: StompConfig(
url: "ws://10.0.2.2:8080/websocket-chat",
onStompError: (StompFrame frame) {
print(
'A stomp error occurred in web socket connection :: ${frame.body}');
},
onWebSocketError: (dynamic frame) {
print(
'A Web socket error occurred in web socket connection :: ${frame.toString()}');
},
onDebugMessage: (dynamic frame) {
print(
'A debug error occurred in web socket connection :: ${frame.toString()}');
},
onConnect: (StompClient client, StompFrame connectFrame) {
print(
'${client.toString()} connected with the following frames ${connectFrame.body}');
_stompClient = client;
Map<String, String> asdf = {};
var clientUnSubscribeFn = _stompClient.subscribe(
destination: "ws://10.0.2.2:8080/user/queue/newMember",
headers: asdf,
callback: (frame) {
// Received a frame for this subscription
print("here" + frame.body);
});
},
),
);
stompClient.activate();
}
sendClientMessage(String msg) async {
Map<String, String> asdf = {};
var clientUnSubscribeFn = await _stompClient.subscribe(
destination: "ws://10.0.2.2:8080/topic/newMember",
headers: asdf,
callback: (frame) {
// Received a frame for this subscription
print("here" + frame.body);
});
_stompClient.send(
destination: "ws://10.0.2.2:8080/app/register",
body: "newUser\n",
headers: asdf);
clientUnSubscribeFn = await _stompClient.subscribe(
destination: "ws://10.0.2.2:8080/user/newUser/msg",
headers: asdf,
callback: (frame) {
// Received a frame for this subscription
print("here" + frame.body);
});
}
Backend code:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/user", "/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket-chat").setAllowedOrigins("*");
}
}
And the controller
#MessageMapping("/register") // 3
#SendToUser("/queue/newMember")
public Set<String> registerUser(String webChatUsername) {
logger.info(("reg"));
if (!connectedUsers.contains(webChatUsername)) {
connectedUsers.add(webChatUsername);
simpMessagingTemplate.convertAndSend("/topic/newMember", webChatUsername); // 4
logger.error(connectedUsers.toString());
return connectedUsers;
} else {
return new HashSet<>();
}
}
#MessageMapping("/unregister") // 5
#SendTo("/topic/disconnectedUser")
public String unregisterUser(String webChatUsername) {
logger.info(("unreg"));
connectedUsers.remove(webChatUsername);
return webChatUsername;
}
#MessageMapping("/message") // 6
public void greeting(WebSocketMessage message) {
logger.warn(("mes"));
simpMessagingTemplate.convertAndSendToUser(message.toWhom, "/msg", message);
In your backend code try this:
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket-chat").setAllowedOrigins("*");
registry.addEndpoint("/websocket-chat").setAllowedOrigins("*").withSockJS();
}
You don't need to supply the full URL again when subscribing/sending messages. So this:
var clientUnSubscribeFn = await _stompClient.subscribe(
destination: "ws://10.0.2.2:8080/topic/newMember",
headers: asdf,
callback: (frame) {
// Received a frame for this subscription
print("here" + frame.body);
});
_stompClient.send(
destination: "ws://10.0.2.2:8080/app/register",
body: "newUser\n",
headers: asdf);
should be
var clientUnSubscribeFn = await _stompClient.subscribe(
destination: "/topic/newMember",
headers: asdf,
callback: (frame) {
// Received a frame for this subscription
print("here" + frame.body);
});
_stompClient.send(
destination: "/app/register",
body: "newUser\n",
headers: asdf);
Related
i want to send event to socket io server that ran on java spring boot application,
the weird point is that my angular app can connect to server perfectly and i can get connect and disconnect events in java.but when it comes to emit an event to server or get any event from server it is not working
Java Class:
public class SocketIOServerImpl {
public static void main(String[] args) throws InterruptedException {
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(8090);
websocket
final SocketIOServer server = new SocketIOServer(config);
server.addConnectListener(socketIOClient -> {
System.out.println("User Connected");
server.getBroadcastOperations().sendEvent("daniyal", "You Connected to Server Successfully");
});
server.addDisconnectListener(client -> {
server.getBroadcastOperations().sendEvent("daniyal", "You Connected to Server Successfully");
});
server.addEventListener("daniyal", String.class, new DataListener<String>() {
#Override
public void onData(SocketIOClient socketIOClient, String s, AckRequest ackRequest) throws Exception {
System.out.println("User Connected");
server.getBroadcastOperations().sendEvent("daniyal", "You Emited STH to Server Successfully");
}
});
server.start();
Thread.sleep(Integer.MAX_VALUE);
server.stop();
}
}
angular servise.ts:
#Injectable({
providedIn: 'root'
})
export class SocketServiceService {
readonly uri: string = 'ws://localhost:8090';
socket: any;
constructor() {
this.socket = io.connect(this.uri,{ transports: [ 'websocket' ] });
}
listen(eventName: string) {
return new Observable((subscriber) => {
this.socket.on(eventName, (data: any) => {
subscriber.next(data);
});
});
}
emit(eventName: string, data: any) {
this.socket.emit(eventName, data);
}
}
app.component.ts:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private socketService: SocketServiceService) {
}
ngOnInit() {
this.socketService.listen('daniyal').subscribe(
(res) => {
console.log('Server Response: ',res);
});
}
emit(){
this.socketService.emit('daniyal','HI SERVER');
}
}
i use angular version 14 and java 11
i expected server can get the event and when send event to client,client get that too
I'm struggling how to send message to all users in a private channel in Spring. Here is my WebSocket config class:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/all", "/message","/topic");
config.setUserDestinationPrefix("/secured/user");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws");
registry.addEndpoint("/ws")
.setHandshakeHandler(new UserHandshakeHandler())
.withSockJS();
}
}
Then I have a list of all my usernames, so I loop my list to send each of them a private message, like this:
#Service
#Slf4j
public class MessageService {
private final SimpMessagingTemplate simpMessagingTemplate;
public MessageService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
#Async
#Scheduled(fixedDelay = 1000, initialDelay = 1000)
public void sendMessageToUser() {
List<String> nameList = Lists.newArrayList("admin", "user");
for(String username : nameList){
simpMessagingTemplate.convertAndSendToUser(username,"/message", "Hello "+username);
}
}
}
Then in my web frontend, I setup a websocket connection like this:
function connect() {
const socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/secured/user/message',
function (message) {
console.log(message);
});
});
}
I got confirmation that frontend successfully establish the websocket connection:
Opening Web Socket...
stomp.min.js:8 Web Socket Opened...
stomp.min.js:8 >>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000
stomp.min.js:8 <<< CONNECTED
version:1.1
heart-beat:0,0
user-name:admin
stomp.min.js:8 connected to server undefined
message.js:18 Connected: CONNECTED
user-name:admin
heart-beat:0,0
version:1.1
stomp.min.js:8 >>> SUBSCRIBE
id:sub-0
destination:/secured/user/message
The problem is the message is indeed sent from server side, but simply not able to be received in the frontend.
Hugely appreciate if anyone could help spot the issue in my code. Thanks a lot!
I made a demo referring to this project (websokets-spring),I mainly added WebSocketListener,this is my code
#Component
#Slf4j
public class WebSocketListener {
#EventListener
public void handleWebSocketConnectListener(SessionConnectEvent sessionConnectEvent) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(sessionConnectEvent.getMessage());
log.debug("connect, sessionId = {}", headerAccessor.getSessionId());
}
#EventListener
public void handleWebSocketConnectedListener(SessionConnectedEvent sessionConnectedEvent) {
log.debug("connected ");
}
#EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent sessionDisconnectEvent) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(sessionDisconnectEvent.getMessage());
log.debug("disconnect, session id = {}", sessionDisconnectEvent.getSessionId());
}
#EventListener
public void handleWebSocketSubscribeEvent(SessionSubscribeEvent sessionSubscribeEvent) {
log.debug("subscribe, {}", sessionSubscribeEvent.toString());
}
#EventListener
public void handleWebSocketUnsubscribeEvent(SessionUnsubscribeEvent sessionUnsubscribeEvent) {
log.debug("unsubscribe, {}", sessionUnsubscribeEvent.getSource());
}
}
When I enter the page to open the WebSocket connection and close the WebSocket connection or Close page, I can listen to the event。enter image description hereenter image description here
But when I use the front-end and back-end separated projects to connect WebSocket, I can listen to the SessionConnectEvent, but not the SessionDisconnectEvententer image description hereenter image description hereenter image description hereenter image description herethis is my front-end stomp code
function chatClient(): CompatClient {
const socket = SockJS('http://localhost/ws');
const client = Stomp.over(socket);
return client;
}
client = chatClient();
client.connect(
{
Authorization: getAuthorization(),
},
(frame: any) => {
console.log("ws success", frame);
client.subscribe(`/user/${userStore.userInfo.username}/messages`, function (message) {
console.log('msg', message.body);
});
}, (err: any) => {
console.log("ws err");
});
When using a proxy across domains, you need to enable websocket true
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
// rewrite: (path: string) => path.replace(/^\/api/, ""),
},
'/ws': {
target: 'http://localhost:8080',
changeOrigin: true,
ws: true
}
},
// port: 80,
open: false,
fs: {
strict: true,
},
}
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;
}
}