[Spring][Websocket] Simple send notifications

Overview Trong bài viết sẽ tạo mộ ứng dụng web thực thi gửi tin nhắn sử dụng tính năng Websocket với Spring Framework 5.0. Websocket là kết nối 2 chiều, song công, liên tục giữa máy chủ và trình duyệt. Sau khi Websocket được thiết lập kết nối, kết nối vẫn mở cho đến khi

Overview

Trong bài viết sẽ tạo mộ ứng dụng web thực thi gửi tin nhắn sử dụng tính năng Websocket với Spring Framework 5.0. Websocket là kết nối 2 chiều, song công, liên tục giữa máy chủ và trình duyệt. Sau khi Websocket được thiết lập kết nối, kết nối vẫn mở cho đến khi client hoặc máy chủ quyết định đóng kết nối này.

Các case sử dụng có thể khi một ứng dụng liên quan nhiều user liên lạc với nhau, từ server đến client chẳng như notification, chat. Chúng ta sẽ build một ứng dụng đơn giản nhận notification sử dụng STOMP messaging với Spring để tạo tương tác qua web khi có một notification được tạo sẽ push message cho các client đã subscribed.

Coding

Dependencies

Trước tiên cần thêm các dependency vào build.gradle.kts như sau

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {id("org.springframework.boot") version "2.5.1"id("io.spring.dependency-management") version "1.0.11.RELEASE"kotlin("jvm") version "1.5.10"kotlin("plugin.spring") version "1.5.10"}

group ="dev.hlk"
version ="0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {mavenCentral()}

dependencies {implementation("org.springframework.boot:spring-boot-starter-web")implementation("com.fasterxml.jackson.module:jackson-module-kotlin")implementation("org.jetbrains.kotlin:kotlin-reflect")implementation("org.jetbrains.kotlin:kotlin-stdlib")// Swagger OpenAPIimplementation("org.springdoc:springdoc-openapi-ui:1.5.9")implementation("org.springdoc:springdoc-openapi-kotlin:1.5.9")// Websocketimplementation("org.springframework:spring-websocket:5.3.8")implementation("org.springframework:spring-messaging:5.3.8")}

tasks.withType<KotlinCompile>{
    kotlinOptions {
        freeCompilerArgs =listOf("-Xjsr305=strict")
        jvmTarget ="11"}}

Sau khi thêm cần phải build lại bằng command ./gradlew build hoặc qua nút

reload của IDEA Intellij

WebSocket Configuration

Tạo một file config cho Websocket:

// config/WebSocketConfig.kt@Configuration@EnableWebSocketMessageBrokerclass WebSocketConfig : WebSocketMessageBrokerConfigurer {overridefunconfigureMessageBroker(registry: MessageBrokerRegistry){
        registry.apply{enableSimpleBroker("/topic")}}overridefunregisterStompEndpoints(registry: StompEndpointRegistry){
        registry.apply{addEndpoint("/notification").withSockJS()}}}

WebSocketConfig được thêm annotation @Configuration để chỉ rằng nó là một configuration class, @EnableWebSocketMessageBroker bật tính năng xử lý Websocket message bởi một message broker.

Method configureMessageBroker() thực thi method mặc định trong WebSocketMessageBrokerConfigurer để config message broker. Nó bắt đầu gọi enableSimpleBroker() để bật một message broker đơn giản trong bộ nhớ chứa tin nhắn cho client với đích có prefix /topic.

Method registerStompEndpoints() đăng ký Websocket endpoint mở cho SockJS client kết nối qua /notification.

Web Controller

Tạo controller NotificationWebController trong /controller/web/NotificationWebController.kt dành cho view.

@Controllerclass NotificationWebController {@GetMapping("/notifications")funindex()=ModelAndView("notifications/index")}

API Controller

Tạo controller NotificationController trong /controller/api/NotificationController.kt dành cho việc push notification.

@RestControllerclassNotificationController(privateval notificationService: NotificationService
){@PostMapping("/pushNotification")funpushNotification(@RequestBody payload: NotificationDao): Boolean {return notificationService.send(payload)}}

Client

Với server-side đã tạo thì bên client cần có phần nhận notification từ server.

Để có thể dùng views cần phải config thêm trong application.yml

spring:mvc:view:prefix: /views/
     suffix: .html

Tạo một index.html trong resources/static/views/notifications/index.html.

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>Websocket</title><!-- CSS --><linkrel="stylesheet"href="../../css/app.css"><linkrel="stylesheet"href="../../css/bootstrap/bootstrap.min.css"><linkrel="stylesheet"href="../../css/bootstrap/bootstrap-grid.min.css"><linkrel="stylesheet"href="../../css/bootstrap/bootstrap-reboot.min.css"><!-- JS --><scriptsrc="../../js/jquery.min.js"></script><scriptsrc="../../js/bootstrap/popper.min.js"></script><scriptsrc="../../js/bootstrap/bootstrap.bundle.min.js"></script><scriptsrc="../../js/bootstrap/bootstrap.min.js"></script><scriptsrc="../../js/sockjs.js"></script><scriptsrc="../../js/stomp.js"></script><scriptsrc="../../js/timeago.full.min.js"></script><scriptsrc="../../js/app.js"></script></head><body><h2>Notifications</h2><ulid="notifications"></ul><divclass="toast"id="js-toast"role="alert"aria-live="assertive"aria-atomic="true"><divclass="toast-header"><svgxmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"version="1.1"id="Layer_1"x="0px"y="0px"viewBox="0 0 512 512"style="enable-background:new 0 0 512 512;"xml:space="preserve"><pathstyle="fill:#FF8A1E;"d="M174.74,430.93c0,44.878,36.382,81.07,81.26,81.07l22.261-103.331L174.74,430.93z"/><pathstyle="fill:#FF562B;"d="M256,512c44.878,0,81.26-36.192,81.26-81.07L256,408.669V512L256,512z"/><polygonstyle="fill:#FFA418;"points="34.846,364.142 34.846,430.93 256,430.93 278.261,341.881 "/><pathstyle="fill:#FFBE11;"d="M256,0C176.159,0,111.837,59.674,99.965,136.479c-5.683,36.763-25.84,146.535-25.84,146.535  l204.137,22.261L256,0z"/><g><pathstyle="fill:#FF8A1E;"d="M437.877,283.016c0,0-20.158-109.774-25.842-146.537C400.162,59.674,335.841,0,256,0v305.277   L437.877,283.016z"/><polygonstyle="fill:#FF8A1E;"points="256,341.881 256,430.93 477.154,430.93 477.154,364.142  "/></g><pathstyle="fill:#FFD460;"d="M256,283.016H74.123c-23.905,18.925-39.277,48.417-39.277,81.126H256l22.261-40.563L256,283.016z"/><pathstyle="fill:#FFA418;"d="M437.877,283.016H256v81.126h221.154C477.154,331.433,461.781,301.941,437.877,283.016z"/><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg><strongclass="js-toast-title mr-auto"></strong><smallclass="js-toast-timestamp"></small><buttontype="button"class="ml-2 mb-1 close"data-dismiss="toast"aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="toast-body js-toast-content"></div></div></body></html>

Tạo app.js trong resources/static/js/app.js

let stompClient =null;functionconnect(){let socket =newSockJS("/notification");
    stompClient = Stomp.over(socket);
    stompClient.connect({},function(frame){
        console.log('Connected: '+ frame);
        stompClient.subscribe("/topic/notifications",function(notification){showNotification(JSON.parse(notification.body));});});// Reconnect socket
    socket.onclose=function(e){
        console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);setTimeout(function(){connect();},1000);}}functiondisconnect(){if(stompClient !=null){
        stompClient.disconnect();}
    console.log("Disconnected");}functionshowNotification(notification){let elem = document.createElement("li");let data ="Title: "+ notification.title
        +" - Timestamp: "+ notification.timestamp
        +" - Content: "+ notification.content;
    elem.appendChild(document.createTextNode(data));$("#notifications").append(elem);$('#js-toast').on('show.bs.toast',function(){let that =$(this);let header = that.children(".toast-header");
        header.children(".js-toast-timestamp").text(timeago.format(Date.parse(notification.timestamp)))
        header.children(".js-toast-title").text(notification.title);
        that.children(".js-toast-content").text(notification.content);})$(".toast").toast("show");}$(function(){disconnect();connect();$(".toast").toast({
        delay:2000// ms});});// Disconnect on close tab
window.onbeforeunload=function(){disconnect();};
  • Phần chính trong đoạn JS là connect()showNotification()
  • connect() function sử dụng SockJSstomp.js mở kết nối đến /notifications, là endpoint SockJS server chờ các kết nối. Khi kết nối thành công, client sẽ subscribe đến đích /topic/notifications phần mà server sẽ publish. Khi nhận notification DOM sẽ chền vào list và có sử dụng Toasts của bootstrap để làm popup.
  • showNotification() function nhận nội dung message để hiển thị.
  • socket.onclose(function(){}) phụ vụ cho việc kết nối lại với server khi server fail/restart.

Demo

Trong demo này cho thấy các trình duyệt nhận notification sau khi thao tác qua swagger gọi 1 API endpoint /notifications và nội dụng gồm title, content, timestamp.

Cảm ơn các bạn đã đọc bài viết. 😉

References

Nguồn: viblo.asia

Bài viết liên quan

9 Mẹo lập trình Web “ẩn mình” giúp tiết kiệm hàng giờ đồng hồ

Hầu hết các lập trình viên (kể cả những người giỏi) đều tốn thời gian x

Can GPT-4o Generate Images? All You Need to Know about GPT-4o-image

OpenAI‘s GPT-4o, introduced on March 25, 2025, has revolutionized the way we create visual con

Khi nào nên dùng main, section, article, header, footer, và aside trong HTML5

HTML5 đã giới thiệu các thẻ ngữ nghĩa giúp cấu trúc nội dung web một cách có

So sánh Webhook và API: Khi nào nên sử dụng?

Trong lĩnh vực công nghệ thông tin và phát triển phần mềm, Webhook và API là hai th