Tạo ứng dụng chat Socket trong Spring Boot

Có lẽ chủ đề về WebSocket rất phổ biến ở mọi ngôn ngữ, framework và nó trở thành một trong số những tính năng không thể thiếu của chúng. Trong bài viết này chúng ta cùng nhau tìm hiểu về WebSocket được giới thiệu trong Spring Framework 4.0.

WebSocket là gì?

WebSocket là một kết nối 2 chiều, song song và liên tục giữa client và server. Sau khi một kết nối WebSocket được thiết lập, nó sẽ mở cho đến khi client hoặc server quyết định đóng kết nối này

Một trường hợp sử dụng điển hình của WebSocket là một ứng dụng có nhiều người dùng giao tiếp với nhau, chẳng hạn như ứng dụng chat.

Trong bài viết này chúng ta sẽ xây dựng một ứng dụng trò chuyện đơn giản để hiểu rõ hơn về cách sử dụng WebSocket trong Spring Boot.

STOMP là gì?

STOMP là viết tắt của Simple Text Oriented Messaging Protocol, nó là một text-based protocol đơn giản được thiết kế để làm việc với các message-oriented middleware (MOM). Mọi ứng dụng client  STOMP đều có thể giao tiếp với bất kỳ STOMP message broker nào và có thể tương tác giữa các ngôn ngữ và nền tản khác nhau.

WebSocket Maven Dependency

Trong bài viết này, mình sẽ sử dụng Maven để quản lý project. Để sử dụng WebSocket trong Spring, chúng ta cần thêm những dependency sau:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

Cấu hình WebSocket trong Spring Boot

Việc đầu tiên chúng ta cần làm để sử dụng WebSocket trong Spring boot đó là tạo class cấu hình WebSocket được chú thích với @EnableWebSocketMessageBroker.

package com.deft.socket.config;

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

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

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

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }
}

@EnableWebSocketMessageBroker annotation được dùng để kích hoạt xử lý các message trong WebSocket được hỗ trợ bởi một message broker.

Method configureMessageBroker() được dùng để cấu hình message borker. Trong đó, nó gọi đến enableSimpleBroker() để sử dụng một message broker đơn giản hoạt động trên memory giúp chuyển các message từ server về lại client trên các đường dẫn có tiền tố là /topic. Ngoài ra nó cũng chỉ định tiền tố /app cho các message gửi từ client lên server. 

Method registerStompEndpoints() đăng ký một /chat endpoint kích hoạt tính năng SockJS fallback để có thể sử dụng các phương tiện vận chuyển thay thế trong trường hợp WebSocket không khả dụng. Ứng dụng client SockJS sẽ cố gắng kết nối với / gs-guide-websocket và sử dụng phương tiện truyền thông tốt nhất hiện có (websocket, xhr-streaming, xhr-polling, v.v.).

Khởi tạo Message Model

Sau khi đã cấu hình xong WebSocket cho project, bây giờ chúng ta cần tạo ra một class đại diện cho các message truyền và nhận giữa client và server.

Message class đại diện cho các message từ các client gửi đến máy chủ. Lưu ý trong phần này mình sử dụng Lombok để triển khai getter, setter, constructor cách tự động.

package com.deft.socket.message;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@Getter
@Setter
public class Message {

    private String from;
    private String text;

}

Và OutputMessage là message mà server trả về cho client.

package com.deft.socket.message;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class OutputMessage {

    private String from;
    private String text;
    private String time;
}

Khởi tạo Message-Handling Controller

Chúng ta có thấy rằng Spring làm việc với STOMP, các message từ STOMP có thể được dẫn đến các @Controller class.

package com.deft.socket.controller;

import com.deft.socket.message.Message;
import com.deft.socket.message.OutputMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import java.text.SimpleDateFormat;
import java.util.Date;

@Controller
public class MessageController {

    @MessageMapping("/chat")
    @SendTo("/topic/messages")
    public OutputMessage send(Message message) throws Exception {
        String time = new SimpleDateFormat("HH:mm").format(new Date());
        return new OutputMessage(message.getFrom(), message.getText(), time);
    }

}

Trong MessageController class, @MessageMapping annotation để ánh xạ một message từ client gửi đến /chat thì send() method sẽ được gọi. Tuy nhiên các bạn để ý một chút, ở đoạn cấu hình WebSocketConfig class, chúng ta đã thêm tiền tố /app cho các message từ client gửi lên server. Nên đường dẫn chính xác để kích hoạt send() method là app/chat.

Sau khi send() method được gọi, sẽ xử lý và khởi tạo một OutputMessage gửi về cho client. OutputMessage object sẽ được chuyển cho tất cả các client đang lắng nghe trên /topic/messages dựa thông tin cấu hình trong @SendTo(“/topic/messages”).

Tạo một Browser client

Các bước cấu hình Websocket trên server đã xong, giờ đây chúng ta cần xây dựng một ứng dụng client đơn giản chạy trên trình duyệt thông qua JavaScript, HTML.

Việc đầu tiên chúng ta cần import 2 thư viện sockjsstomp trong Javascript. Sau đó, cần cài đặt một hàm connect() để mở kết nối đến server, sendMessage() để gửi các STOMP message và disconnect() để ngắt kết nối.

<html>
<head>
    <title>Chat WebSocket</title>
    <script type="text/javascript" src="https://unpkg.com/[email protected]/sockjs-0.3.4.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js" integrity="sha512-tL4PIUsPy+Rks1go4kQG8M8/ItpRMvKnbBjQm4d2DQnFwgcBYRRN00QdyQnWSCwNMsoY/MfJY8nHp2CzlNdtZA==" crossorigin="anonymous"></script>
    <script type="text/javascript">
            var stompClient = null;

            function setConnected(connected) {
                document.getElementById('connect').disabled = connected;
                document.getElementById('disconnect').disabled = !connected;
                document.getElementById('conversationDiv').style.visibility
                  = connected ? 'visible' : 'hidden';
                document.getElementById('response').innerHTML = '';
            }

            function connect() {
                var socket = new SockJS('/gs-guide-websocket');
                stompClient = Stomp.over(socket);
                stompClient.connect({}, function(frame) {
                    setConnected(true);
                    console.log('Connected: ' + frame);
                    stompClient.subscribe('/topic/messages', function(messageOutput) {
                        showMessageOutput(JSON.parse(messageOutput.body));
                    });
                });
            }

            function disconnect() {
                if(stompClient != null) {
                    stompClient.disconnect();
                }
                setConnected(false);
                console.log("Disconnected");
            }

            function sendMessage() {
                var from = document.getElementById('from').value;
                var text = document.getElementById('text').value;
                stompClient.send("/app/chat", {},
                  JSON.stringify({'from':from, 'text':text}));
            }

            function showMessageOutput(messageOutput) {
                var response = document.getElementById('response');
                var p = document.createElement('p');
                p.style.wordWrap = 'break-word';
                p.appendChild(document.createTextNode(messageOutput.from + ": "
                  + messageOutput.text + " (" + messageOutput.time + ")"));
                response.appendChild(p);
            }
        </script>
</head>
<body onload="disconnect()">
<div>
    <div>
        <input type="text" id="from" placeholder="Choose a nickname"/>
    </div>
    <br />
    <div>
        <button id="connect" onclick="connect();">Connect</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">
            Disconnect
        </button>
    </div>
    <br />
    <div id="conversationDiv">
        <input type="text" id="text" placeholder="Write a message..."/>
        <button id="sendMessage" onclick="sendMessage();">Send</button>
        <p id="response"></p>
    </div>
</div>

</body>
</html>

Lưu ý mình tạo một file index.html đặt trong thư mục src/main/resources/static/index.html đây là static folder mà mặc định spring boot đã cấu hình tự động cho chúng ta. Giờ đây mình chỉ cần vào trình duyệt truy cập vào http://localhost:8080/ thì server sẽ trả về index.html chạy trên client.

Kết bài

Qua bài viết chúng ta đã biết cách tạo một ứng dụng chat đơn giản sử dụng WebSocket trong Spring Boot.

Mã nguồn được mình công khai trên gitlab để các bạn có thể tiện tham khảo: socket 

Nguồn tham khảo

https://spring.io/guides/gs/messaging-stomp-websocket/

https://www.baeldung.com/websockets-spring

 

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x