使用 WebSocket 構建互動式 Web 應用程式

本指南將引導您建立可在瀏覽器和伺服器之間傳送和接收訊息的“Hello, world”應用程式。WebSocket 是 TCP 之上的一層輕量級協議,因此適合使用“子協議”來嵌入訊息。在本指南中,我們使用 Spring 的 STOMP 訊息傳遞功能來構建互動式 Web 應用程式。STOMP 是執行在底層 WebSocket 之上的一個子協議。

您將構建什麼

您將構建一個接受包含使用者姓名的訊息的伺服器。作為響應,伺服器會將問候語推送到客戶端訂閱的佇列中。

您需要準備什麼

如何完成本指南

與大多數 Spring 入門指南一樣,您可以從頭開始並完成每個步驟,也可以跳過您已經熟悉的基本設定步驟。無論哪種方式,您最終都能獲得可執行的程式碼。

從頭開始,請轉到從 Spring Initializr 開始

跳過基礎部分,請執行以下操作:

完成後,您可以對照 gs-messaging-stomp-websocket/complete 目錄中的程式碼檢查您的結果。

從 Spring Initializr 開始

您可以使用此預初始化專案,然後點選生成下載 ZIP 檔案。此專案已配置好,適合本教程中的示例。

手動初始化專案

  1. 導航到 https://start.spring.io。此服務會為您拉取應用程式所需的所有依賴項,並完成大部分設定。

  2. 選擇 Gradle 或 Maven,以及您想要使用的語言。本指南假設您選擇了 Java。

  3. 點選 Dependencies 並選擇 Websocket

  4. 點選 Generate

  5. 下載生成的 ZIP 檔案,其中包含根據您的選擇配置好的 Web 應用程式。

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此過程。
您也可以從 Github fork 該專案,然後在您的 IDE 或其他編輯器中開啟。

建立資源表示類

現在您已經設定好了專案和構建系統,您可以建立您的 STOMP 訊息服務了。

透過思考服務間的互動來開始此過程。

服務將接受 STOMP 訊息,其主體是一個 JSON 物件,包含一個名稱。如果名稱是 Fred,訊息可能類似於以下內容:

{
    "name": "Fred"
}

為了建模攜帶名稱的訊息,您可以建立一個簡單的 Java 物件,包含一個 name 屬性和相應的 getName() 方法,如以下清單(來自 src/main/java/com/example/messagingstompwebsocket/HelloMessage.java)所示:

package com.example.messagingstompwebsocket;

public class HelloMessage {

  private String name;

  public HelloMessage() {
  }

  public HelloMessage(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

接收到訊息並提取名稱後,服務將透過建立問候語並將其釋出到客戶端訂閱的獨立佇列中進行處理。問候語也將是一個 JSON 物件,如以下清單所示:

{
    "content": "Hello, Fred!"
}

為了建模問候語表示,請新增另一個簡單的 Java 物件,包含一個 content 屬性和相應的 getContent() 方法,如以下清單(來自 src/main/java/com/example/messagingstompwebsocket/Greeting.java)所示:

package com.example.messagingstompwebsocket;

public class Greeting {

  private String content;

  public Greeting() {
  }

  public Greeting(String content) {
    this.content = content;
  }

  public String getContent() {
    return content;
  }

}

Spring 將使用 Jackson JSON 庫自動將 Greeting 型別的例項轉換為 JSON。

接下來,您將建立一個控制器來接收 hello 訊息併發送問候訊息。

建立訊息處理控制器

在 Spring 處理 STOMP 訊息的方式中,STOMP 訊息可以路由到 @Controller 類。例如,GreetingController(來自 src/main/java/com/example/messagingstompwebsocket/GreetingController.java)被對映處理傳送到 /hello 目的地的訊息,如以下清單所示:

package com.example.messagingstompwebsocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

@Controller
public class GreetingController {


  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
  }

}

這個控制器簡潔明瞭,但其中有很多事情在發生。我們將逐步分解說明。

@MessageMapping 註解確保如果訊息傳送到 /hello 目的地,將呼叫 greeting() 方法。

訊息的有效載荷被繫結到一個 HelloMessage 物件,該物件被傳遞給 greeting() 方法。

在內部,方法的實現透過讓執行緒休眠一秒鐘來模擬處理延遲。這是為了演示客戶端傳送訊息後,伺服器可以非同步處理訊息,所需時間不限。客戶端無需等待響應,可以繼續執行任何需要完成的工作。

一秒延遲後,greeting() 方法建立一個 Greeting 物件並返回。返回值會被廣播到 /topic/greetings 的所有訂閱者,如 @SendTo 註解所示。請注意,輸入訊息中的名稱已被淨化處理,因為在這種情況下,它將被回顯並在客戶端的瀏覽器 DOM 中重新渲染。

配置 Spring 以支援 STOMP 訊息傳遞

服務的基本元件已建立完成,現在您可以配置 Spring 以啟用 WebSocket 和 STOMP 訊息傳遞。

建立一個名為 WebSocketConfig 的 Java 類,類似於以下清單(來自 src/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java):

package com.example.messagingstompwebsocket;

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 WebSocketConfig implements WebSocketMessageBrokerConfigurer {

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

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

}

WebSocketConfig 使用 @Configuration 註解,表明它是一個 Spring 配置類。它還使用 @EnableWebSocketMessageBroker 註解。顧名思義,@EnableWebSocketMessageBroker 啟用了由訊息代理支援的 WebSocket 訊息處理。

configureMessageBroker() 方法實現了 WebSocketMessageBrokerConfigurer 中的預設方法來配置訊息代理。它首先呼叫 enableSimpleBroker() 啟用一個簡單的基於記憶體的訊息代理,將問候訊息傳送回客戶端,目的地以 /topic 為字首。它還指定 /app 作為使用 @MessageMapping 註解的方法的訊息字首。這個字首將用於定義所有的訊息對映。例如,/app/helloGreetingController.greeting() 方法對映處理的端點。

registerStompEndpoints() 方法註冊 /gs-guide-websocket 端點用於 WebSocket 連線。

建立瀏覽器客戶端

伺服器端元件已經就緒,現在您可以將注意力轉向 JavaScript 客戶端,它將向伺服器傳送訊息並從伺服器接收訊息。

建立一個 index.html 檔案,類似於以下清單(來自 src/main/resources/static/index.html):

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" crossorigin="anonymous">
    <link href="/main.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@stomp/[email protected]/bundles/stomp.umd.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

這個 HTML 檔案匯入了 StompJS JavaScript 庫,該庫將用於透過 WebSocket 上的 STOMP 與我們的伺服器通訊。我們還匯入了 app.js,其中包含客戶端應用程式的邏輯。以下清單(來自 src/main/resources/static/app.js)展示了該檔案:

const stompClient = new StompJs.Client({
    brokerURL: 'ws://:8080/gs-guide-websocket'
});

stompClient.onConnect = (frame) => {
    setConnected(true);
    console.log('Connected: ' + frame);
    stompClient.subscribe('/topic/greetings', (greeting) => {
        showGreeting(JSON.parse(greeting.body).content);
    });
};

stompClient.onWebSocketError = (error) => {
    console.error('Error with websocket', error);
};

stompClient.onStompError = (frame) => {
    console.error('Broker reported error: ' + frame.headers['message']);
    console.error('Additional details: ' + frame.body);
};

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    stompClient.activate();
}

function disconnect() {
    stompClient.deactivate();
    setConnected(false);
    console.log("Disconnected");
}

function sendName() {
    stompClient.publish({
        destination: "/app/hello",
        body: JSON.stringify({'name': $("#name").val()})
    });
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

$(function () {
    $("form").on('submit', (e) => e.preventDefault());
    $( "#connect" ).click(() => connect());
    $( "#disconnect" ).click(() => disconnect());
    $( "#send" ).click(() => sendName());
});

此 JavaScript 檔案中需要理解的主要部分是 stompClient.onConnectsendName 函式。

stompClient 使用 brokerURL 初始化,該 URL 指向路徑 /gs-guide-websocket,這是我們的 WebSocket 伺服器等待連線的地方。成功連線後,客戶端將訂閱 /topic/greetings 目的地,伺服器將在此處釋出問候訊息。在該目的地收到問候語時,它會將一個段落元素附加到 DOM 中以顯示問候語訊息。

sendName() 函式檢索使用者輸入的名稱,並使用 STOMP 客戶端將其傳送到 /app/hello 目的地(GreetingController.greeting() 方法將在此處接收)。

如果您願意,可以省略 main.css,或者建立一個空檔案,以便 <link> 標籤能夠解析。

使應用程式可執行

Spring Boot 會為您建立一個應用程式類。在這種情況下,它不需要進一步修改。您可以使用它來執行此應用程式。以下清單(來自 src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java)展示了該應用程式類:

package com.example.messagingstompwebsocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MessagingStompWebsocketApplication {

  public static void main(String[] args) {
    SpringApplication.run(MessagingStompWebsocketApplication.class, args);
  }
}

@SpringBootApplication 是一個方便的註解,它包含了以下所有內容:

  • @Configuration:將類標記為應用程式上下文的 bean 定義源。

  • @EnableAutoConfiguration:告訴 Spring Boot 根據類路徑設定、其他 bean 和各種屬性設定開始新增 bean。例如,如果類路徑中有 spring-webmvc,此註解會將應用程式標記為 Web 應用程式,並激活關鍵行為,例如設定 DispatcherServlet

  • @ComponentScan:告訴 Spring 在 com/example 包中查詢其他元件、配置和服務,從而找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法啟動應用程式。您有沒有注意到沒有一行 XML?也沒有 web.xml 檔案。這個 Web 應用程式是 100% 純 Java,您無需處理任何底層配置或基礎設施。

構建可執行 JAR

您可以使用 Gradle 或 Maven 從命令列執行應用程式。您還可以構建一個包含所有必要依賴項、類和資源的單個可執行 JAR 檔案並執行它。構建可執行 JAR 可以輕鬆地在整個開發生命週期、不同環境等中作為應用程式進行交付、版本控制和部署。

如果您使用 Gradle,可以使用 ./gradlew bootRun 執行應用程式。或者,您可以使用 ./gradlew build 構建 JAR 檔案,然後按如下方式執行 JAR 檔案:

java -jar build/libs/gs-messaging-stomp-websocket-0.1.0.jar

如果您使用 Maven,可以使用 ./mvnw spring-boot:run 執行應用程式。或者,您可以使用 ./mvnw clean package 構建 JAR 檔案,然後按如下方式執行 JAR 檔案:

java -jar target/gs-messaging-stomp-websocket-0.1.0.jar
此處描述的步驟建立了一個可執行的 JAR。您還可以構建一個經典的 WAR 檔案

將顯示日誌輸出。服務應在幾秒鐘內啟動並執行。

測試服務

現在服務已執行,將瀏覽器指向 https://:8080 並點選 Connect 按鈕。

開啟連線後,系統會要求您輸入姓名。輸入您的姓名並點選 Send。您的姓名將透過 STOMP 作為 JSON 訊息傳送到伺服器。在一秒鐘的模擬延遲後,伺服器會發送一條帶有“Hello”問候語的訊息,該訊息將顯示在頁面上。此時,您可以傳送另一個姓名,或者點選 Disconnect 按鈕關閉連線。

總結

恭喜!您剛剛使用 Spring 開發了一個基於 STOMP 的訊息服務。

另請參閱

以下指南也可能對您有所幫助:

想撰寫新指南或貢獻現有指南?請檢視我們的貢獻指南

所有指南的程式碼都採用 ASLv2 許可釋出,文字內容採用署名-禁止演繹知識共享許可

獲取程式碼