Gửi private message với @SendToUser annotation trong Spring WebSocket

1019

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

Ở bài viết hướng dẫn các bạn cách hiện thực WebSocket với Spring WebSocket, ứng dụng ví dụ của mình sẽ cho phép tất cả các client nhận được tất cả các message khi chúng subscribe vào endpoint, còn ở bài viết hướng dẫn gửi message tới một user cụ thể nào đó, ứng dụng ví dụ của mình cho phép client có thể gửi một message tới một user gắn liền với một sessionId cụ thể. Điểm hơi bất tiện ở đây nếu nhu cầu của chúng ta chỉ là gửi một request tới WebSocket server, cần nó xử lý business logic và trả về kết quả cho chính chúng ta mà thôi, tất nhiên là các bạn cũng có thể giải quyết bài toán này bằng cách gửi STOMP message như mình đã hướng dẫn, nhưng chúng ta cũng phải gán thêm user gắn liền với sessionId để có thể làm được điều này. Sử dụng @SendToUser annotation của Spring WebSocket các bạn sẽ không cần gán thêm thông tin user này. Cụ thể như thế nào? Mình sẽ hướng dẫn các bạn trong bài viết này các bạn nhé.

  Discord đã lưu trữ hàng tỉ messages mỗi ngày như thế nào
  Giới thiệu JMS – Java Message Services

Mình cũng tạo mới một Spring Boot với Web và WebSocket dependency.

Kết quả như sau:

Gửi private message với @SendToUser annotation trong Spring WebSocket

Mình cũng khai báo để sử dụng WebJars với JQuerySocketJS client và Stomp WebSocket dependencies như sau:

<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.4</version>
</dependency>

Class WebSocketConfiguration để cấu hình WebSocket:

package com.huongdanjava.springboot.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("queue");
registry.setApplicationDestinationPrefixes("/app");
}
}

Chúng ta sẽ sử dụng annotation @SendToUser trong controller handle request message từ phía client như sau:

package com.huongdanjava.springboot.websocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;

@Controller
public class MessageController {

@MessageMapping("/hello")
@SendToUser("/queue/reply")
public String send(String username) {
return "Hello, " + username;
}
}

Giá trị được khai báo trong annotation @SendToUser chính là endpoint mà client sẽ subcribe để nhận được message từ WebSocket server nha các bạn!

Về bản chất thì Spring cũng sử dụng endpoint gửi message tới một user nào đó với prefix mặc định là “/user”. Các bạn cũng có thể thay đổi prefix mặc định này bằng cách sử dụng phương thức setUserDestinationPrefix() của đối tượng MessageBrokerRegistry trong class WebSocketConfiguration như mình đã làm trong bài viết trước. Điểm khác biệt ở đây là thay vì chúng ta cần phải quản lý user gắn liền với sessionId của WebSocket connection, bây giờ Spring sẽ sử dụng sessionId để chỉ gửi message cho riêng client đang request mà thôi!

Bây giờ mình sẽ thêm code phần front-end thử xem ví dụ của chúng ta hoạt động như thế nào!

index.html:

<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<script src="/webjars/jquery/dist/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/app.js"></script>
</head>
<body>
<div id="main-content">
<div>
<form>
<div>
<label>What is your name?</label> 
<input type="text" id="name" placeholder="Your name here...">
</div>
<button id="send" type="submit">Send</button>
</form>
</div>
</div>
<div>
<label>Message from server: </label><span id="message"></span>
</div>
</body>
</html>

app.js

var stompClient = null;

$(document).ready(function() {
connect();
});

function connect() {
var socket = new SockJS('/hello');
stompClient = Stomp.over(socket);
stompClient.connect({}, function() {
console.log('Web Socket is connected');
stompClient.subscribe('/user/queue/reply', function(message) {
$("#message").text(message.body);
});
});
}

$(function() {
$("form").on('submit', function(e) {
e.preventDefault();
});
$("#send").click(function() {
stompClient.send("/app/hello", {}, $("#name").val());
});
});

Như các bạn thấy, ở phía client, chúng ta sẽ subscribe vào endpoint với prefix mặc định bắt đầu là “/user” như bài viết trước.

Bây giờ, nếu mình sẽ mở 2 cửa sổ trình duyệt, nhập tên mình vào ô “What is your name”, nhấn nút Send, các bạn sẽ thấy chỉ cửa sổ mình đang làm việc nhận được message từ WebSocket server: