Mục lục
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 sockjs và stomp 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