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

本指南將引導您完成建立一個“Hello, world”應用程式的過程,該應用程式在瀏覽器和伺服器之間來回傳送訊息。WebSocket 是 TCP 之上一個輕量級的層。這使得它適合使用“子協議”來嵌入訊息。在本指南中,我們使用 STOMP 訊息與 Spring 來建立一個互動式 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. 單擊生成

  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 中重新呈現。

為 STOMP 訊息配置 Spring

現在已經建立了服務的基本元件,您可以配置 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。它還為繫結到用 @MessageMapping 註解的方法的訊息指定 /app 字首。此此綴將用於定義所有訊息對映。例如,/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" 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 庫,該庫將用於透過 STOMP over websocket 與我們的伺服器通訊。我們還匯入了 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 路徑,這是我們的 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://:8080 並單擊 Connect 按鈕。

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

總結

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

另請參閱

以下指南也可能有所幫助

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

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

獲取程式碼