The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Using WebSockets

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

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • 安装JDK 11以上版本并正确配置了 JAVA_HOME

  • Apache Maven 3.9.1

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

Architecture

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

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.

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

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:

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

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

关于如何安装并使用Quarkus CLI的更多信息,请参考Quarkus CLI指南

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

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

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

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:

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

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")
If you only want to use the WebSocket client you should include quarkus-websockets-client instead.

Handling web sockets

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) {
        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) {
        if (message.equalsIgnoreCase("_ready_")) {
            broadcast("User " + username + " joined");
        } else {
            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

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:

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

Then open your 2 browser windows to 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

Application

As usual, the application can be packaged using:

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

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

You can also build the native executable using:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

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

WebSocket Clients

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.

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

The Quarkus WebSocket implementation is an implementation of Jakarta Websockets.