编辑此页面

将 WebSockets 与 Undertow 一起使用

本指南将解释 Quarkus 应用程序如何在 Undertow 基础的 Quarkus 应用程序中,或者在依赖 Jakarta WebSocket 的情况下,利用 WebSockets 创建交互式 Web 应用程序。

如果您不使用 Undertow 或 Jakarta WebSocket,建议使用更现代的 WebSockets Next 扩展

由于这是标准的 WebSocket 应用程序,我们将创建一个简单的聊天应用程序。

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

架构

在本指南中,我们将创建一个简单的聊天应用程序,使用 WebSockets 接收和发送消息给其他已连接的用户。

Architecture

解决方案

我们建议您按照以下部分的说明逐步创建应用程序。但是,您可以直接跳到完成的示例。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 存档

解决方案位于 websockets-quickstart 目录

创建 Maven 项目

首先,我们需要一个新项目。使用以下命令创建一个新项目

CLI
quarkus create app org.acme:websockets-quickstart \
    --extension='websockets' \
    --no-code
cd websockets-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=websockets-quickstart \
    -Dextensions='websockets' \
    -DnoCode
cd websockets-quickstart

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=websockets-quickstart"

此命令会生成项目(不包含任何类)并导入 websockets 扩展。

如果您已经配置了 Quarkus 项目,可以通过在项目根目录运行以下命令将 websockets 扩展添加到项目中

CLI
quarkus extension add websockets
Maven
./mvnw quarkus:add-extension -Dextensions='websockets'
Gradle
./gradlew addExtension --extensions='websockets'

这会将以下内容添加到您的构建文件中

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-websockets</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-websockets")
如果您只想使用 WebSocket 客户端,则应包含 quarkus-websockets-client

处理 WebSockets

我们的应用程序包含一个处理 WebSockets 的类。在 src/main/java 目录中创建 org.acme.websockets.ChatSocket 类。将以下内容复制到创建的文件中

package org.acme.websockets;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import jakarta.websocket.Session;

@ServerEndpoint("/chat/{username}")         (1)
@ApplicationScoped
public class ChatSocket {

    Map<String, Session> sessions = new ConcurrentHashMap<>(); (2)

    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        broadcast("User " + username + " joined");
        sessions.put(username, session);
    }

    @OnClose
    public void onClose(Session session, @PathParam("username") String username) {
        sessions.remove(username);
        broadcast("User " + username + " left");
    }

    @OnError
    public void onError(Session session, @PathParam("username") String username, Throwable throwable) {
        sessions.remove(username);
        broadcast("User " + username + " left on error: " + throwable);
    }

    @OnMessage
    public void onMessage(String message, @PathParam("username") String username) {
        broadcast(">> " + username + ": " + message);
    }

    private void broadcast(String message) {
        sessions.values().forEach(s -> {
            s.getAsyncRemote().sendObject(message, result ->  {
                if (result.getException() != null) {
                    System.out.println("Unable to send message: " + result.getException());
                }
            });
        });
    }

}
1 配置 WebSocket URL
2 存储当前打开的 WebSockets

一个简洁的 Web 前端

所有聊天应用程序都需要一个漂亮的 UI,嗯,这个可能不是最漂亮的,但能完成工作。Quarkus 会自动服务 META-INF/resources 目录中的静态资源。创建 src/main/resources/META-INF/resources 目录并将此 index.html 文件复制进去。

运行应用程序

现在,让我们看看我们的应用程序的运行情况。使用以下命令运行它

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

然后打开 2 个浏览器窗口访问 https://:8080/

  1. 在顶部的文本区域输入一个名字(使用 2 个不同的名字)。

  2. 点击连接

  3. 发送和接收消息

Application

与往常一样,可以使用以下命令打包应用程序

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

并使用 java -jar target/quarkus-app/quarkus-run.jar 执行。

您也可以使用以下命令构建本机可执行文件

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

您也可以使用此处详细介绍的方法来测试您的 WebSocket 应用程序 here

WebSocket 客户端

Quarkus 还包含一个 WebSocket 客户端。您可以调用 ContainerProvider.getWebSocketContainer().connectToServer 来创建 WebSocket 连接。默认情况下,quarkus-websockets artifact 同时包含客户端和服务器支持。但是,如果您只想使用客户端,可以改用 quarkus-websockets-client

当您连接到服务器时,您可以传入您想使用的带注解的客户端端点的类,或者 jakarta.websocket.Endpoint 的一个实例。如果您使用的是带注解的端点,则可以使用与服务器上相同的注解,只是它必须被注解为 @ClientEndpoint 而不是 @ServerEndpoint

下面的示例展示了用于测试上方聊天端点的客户端。

package org.acme.websockets;

import java.net.URI;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

import jakarta.websocket.ClientEndpoint;
import jakarta.websocket.ContainerProvider;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class ChatTest {

    private static final LinkedBlockingDeque<String> MESSAGES = new LinkedBlockingDeque<>();

    @TestHTTPResource("/chat/stu")
    URI uri;

    @Test
    public void testWebsocketChat() throws Exception {
        try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) {
            Assertions.assertEquals("CONNECT", MESSAGES.poll(10, TimeUnit.SECONDS));
            Assertions.assertEquals("User stu joined", MESSAGES.poll(10, TimeUnit.SECONDS));
            session.getAsyncRemote().sendText("hello world");
            Assertions.assertEquals(">> stu: hello world", MESSAGES.poll(10, TimeUnit.SECONDS));
        }
    }

    @ClientEndpoint
    public static class Client {

        @OnOpen
        public void open(Session session) {
            MESSAGES.add("CONNECT");
            // Send a message to indicate that we are ready,
            // as the message handler may not be registered immediately after this callback.
            session.getAsyncRemote().sendText("_ready_");
        }

        @OnMessage
        void message(String msg) {
            MESSAGES.add(msg);
        }

    }

}

更多 WebSocket 信息

Quarkus WebSocket 实现是 Jakarta Websockets 的实现。

相关内容