Using WebSockets

本指南解释了如何让你的 Quarkus 应用程序利用 WebSockets 创建交互式 Web 应用程序。因为它是一个 canonical WebSocket 应用程序,所以我们要创建一个简单的聊天应用程序。

This guide explains how your Quarkus application can utilize web sockets to create interactive web applications. Because it’s the canonical web socket application, we are going to create a simple chat application.

Prerequisites

Unresolved directive in websockets.adoc - include::{includes}/prerequisites.adoc[]

Architecture

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

In this guide, we create a straightforward chat application using web sockets to receive and send messages to the other connected users.

websocket guide architecture

Solution

我们建议你按照后续章节中的说明,逐步创建应用程序。但是,你可以直接跳到已完成的示例。

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can skip right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

解决方案位于 websockets-quickstart directory

The solution is located in the websockets-quickstart directory.

Creating the Maven project

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

First, we need a new project. Create a new project with the following command:

Unresolved directive in websockets.adoc - include::{includes}/devtools/create-app.adoc[]

此命令生成项目(没有任何类),并导入 websockets 扩展。

This command generates the project (without any classes) and imports the websockets extension.

如果你已配置你的 Quarkus 项目,则可以通过在项目基本目录中运行以下命令向你的项目添加 websockets 扩展:

If you already have your Quarkus project configured, you can add the websockets extension to your project by running the following command in your project base directory:

Unresolved directive in websockets.adoc - include::{includes}/devtools/extension-add.adoc[]

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

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-websockets</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-websockets")

如果你只想使用 WebSocket 客户端,你应该用 quarkus-websockets-client 代替。

If you only want to use the WebSocket client you should include quarkus-websockets-client instead.

Handling web sockets

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

Our application contains a single class that handles the web sockets. Create the org.acme.websockets.ChatSocket class in the src/main/java directory. Copy the following content into the created file:

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 Configures the web socket URL
2 Stores the currently opened web sockets

A slick web frontend

所有聊天应用程序都需要 nice UI,此示例可能不太美观,但能完成工作。Quarkus 自动为 META-INF/resources 目录中包含的静态资源提供服务。创建 src/main/resources/META-INF/resources 目录并将此 index.html 文件复制到其中。

All chat applications need a nice UI, well, this one may not be that nice, but does the work. Quarkus automatically serves static resources contained in the META-INF/resources directory. Create the src/main/resources/META-INF/resources directory and copy this index.html file in it.

Run the application

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

Now, let’s see our application in action. Run it with:

Unresolved directive in websockets.adoc - include::{includes}/devtools/dev.adoc[]

然后在 2 个浏览器窗口中打开 [role="bare"][role="bare"]http://localhost:8080/:

Then open your 2 browser windows to [role="bare"]http://localhost:8080/:

  1. Enter a name in the top text area (use 2 different names).

  2. Click on connect

  3. Send and receive messages

websocket guide screenshot

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

As usual, the application can be packaged using:

Unresolved directive in websockets.adoc - include::{includes}/devtools/build.adoc[]

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

And executed using java -jar target/quarkus-app/quarkus-run.jar.

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

You can also build the native executable using:

Unresolved directive in websockets.adoc - include::{includes}/devtools/build-native.adoc[]

你还可以使用 here 中详述的方法测试 WebSocket 应用程序。

You can also test your web socket applications using the approach detailed here.

WebSocket Clients

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

Quarkus also contains a WebSocket client. You can call ContainerProvider.getWebSocketContainer().connectToServer to create a websocket connection. By default, the quarkus-websockets artifact includes both client and server support. However, if you only want the client you can include quarkus-websockets-client instead.

当连接到服务器时,你可以传入想要使用的注释客户端终端的类,或 jakarta.websocket.Endpoint 的实例。如果你正在使用注释终端,则可以使用与在服务器上完全相同的注释,除了必须使用 @ClientEndpoint 而不是 @ServerEndpoint 注释外。

When you connect to the server you can either pass in the Class of the annotated client endpoint you want to use, or an instance of jakarta.websocket.Endpoint. If you are using the annotated endpoint then you can use the exact same annotations as you can on the server, except it must be annotated with @ClientEndpoint instead of @ServerEndpoint.

以下示例展示了客户端被用来测试上述聊天终端。

The example below shows the client being used to test the chat endpoint above.

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);
        }

    }

}

More WebSocket Information

Quarkus WebSocket 实现是 Jakarta Websockets 的实现。

The Quarkus WebSocket implementation is an implementation of Jakarta Websockets.