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

本指南將引導您建立一個「Hello, world」應用程式,該應用程式會在瀏覽器和伺服器之間來回傳送訊息。WebSocket 是 TCP 之上的一個薄而輕量的層。這使其適合使用「子協定」來嵌入訊息。在本指南中,我們使用 STOMP 訊息傳遞與 Spring 來建立互動式 Web 應用程式。STOMP 是一個在較低層級 WebSocket 之上運作的子協定。

您將建置的內容

您將建置一個伺服器,該伺服器接受攜帶使用者名稱的訊息。作為回應,伺服器會將問候語推送到用戶端訂閱的佇列中。

您需要的東西

如何完成本指南

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

若要從頭開始,請繼續前往 從 Spring Initializr 開始

若要跳過基礎知識,請執行以下操作

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

從 Spring Initializr 開始

您可以使用這個預先初始化的專案,然後點擊「Generate」下載 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"
}

要對攜帶名稱的訊息進行建模,您可以建立一個具有 name 屬性和相應的 getName() 方法的普通 Java 物件,如下面清單(來自 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!"
}

要對問候語表示形式進行建模,請新增另一個具有 content 屬性和相應的 getContent() 方法的普通 Java 物件,如下面清單(來自 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() 方法為 websocket 連線註冊 /gs-guide-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" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" 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://127.0.0.1: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 使用指向路徑 /gs-guide-websocketbrokerURL 進行初始化,這是我們的 websockets 伺服器等待連線的位置。成功連線後,用戶端會訂閱 /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://127.0.0.1:8080 並點擊 Connect 按鈕。

開啟連線後,系統會要求您輸入姓名。 輸入您的姓名並點擊 Send。 您的姓名會透過 STOMP 以 JSON 訊息的形式傳送至伺服器。 在經過一秒鐘的模擬延遲後,伺服器會回傳一則包含 “Hello” 問候語的訊息,並顯示在頁面上。此時,您可以傳送另一個姓名,或者您可以點擊 Disconnect 按鈕來關閉連線。

總結

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

參見

以下指南可能也很有幫助

想要撰寫新的指南或貢獻現有的指南嗎? 請查看我們的 貢獻指南

所有指南都以 ASLv2 授權發布程式碼,並以 Attribution, NoDerivatives 創用 CC 授權發布寫作內容。

取得程式碼