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

WebSocketの使用

このガイドでは、QuarkusアプリケーションがWebSocketを利用してインタラクティブなウェブアプリケーションを作成する方法を説明します。 定型的な WebSocketアプリケーションを想定し、簡単なチャットアプリケーションを作成します。

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

アーキテクチャ

このガイドでは、WebSocketを使用して、接続されている他のユーザーとメッセージを送受信するための簡単なチャットアプリケーションを作成します。

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:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=websockets-quickstart \
    -Dextensions="websockets" \
    -DnoCode
cd websockets-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

このコマンドは、Mavenプロジェクト(クラスなし)を生成し、 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 を含める必要があります。

WebSocketの取り扱い

このアプリケーションには、Web ソケットを処理するクラスが一つ含まれます。 src/main/java ディレクトリーに org.acme.websockets.ChatSocket クラスを作成します。作成したファイルに以下の内容をコピーします。

package org.acme.websockets;

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

import javax.enterprise.context.ApplicationScoped;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import javax.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 WebSocketのURLを設定する
2 現在開いているWebSocketを格納します。

洗練された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つ開いて、 http://localhost:8080/ に移動します:

  1. 上部のテキストエリアに名前を入力します(2種類の名前を使用します)。

  2. connectをクリック

  3. メッセージの送受信

Application

いつものように、アプリケーションは以下の方法でパッケージ化されます。

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

そして、 java -jar target/quarkus-app/quarkus-run.jar を使って実行します。

ネイティブ実行可能ファイルを次のようにビルドすることもできます。

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

また、https://github.com/quarkusio/quarkus-quickstarts/blob/main/websockets-quickstart/src/test/java/org/acme/websockets/ChatTest.java[こちら] で詳細に解説された手法を使用して、Webソケットアプリケーションをテストすることもできます。

WebSocketクライアント

Quarkusには、WebSocketクライアントも含まれています。 ContainerProvider.getWebSocketContainer().connectToServer を呼び出して、WebSocket 接続を作成できます。デフォルトでは、 quarkus-websockets アーティファクトにはクライアントとサーバーの両方が含まれていますが、クライアントのみを必要とする場合は、代わりに quarkus-websockets-client を含めることができます。

サーバーに接続する際には、使用するアノテーション付きクライアント・エンドポイントの Class で渡すか、 javax.websocket.Endpoint のインスタンスで渡すことができます。アノテーション付きエンドポイントを使用している場合は、サーバー上で使用できるのとまったく同じアノテーションを使用できますが、アノテーションは @ServerEndpoint ではなく @ClientEndpoint でなければなりません。

以下の例は、上記のチャットエンドポイントをテストするために使用されるクライアントを示しています。

package org.acme.websockets;

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

import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.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 Websocket の実装の一つです。