WebSocket でインタラクティブ Web アプリケーション作成

このガイドでは、ブラウザーとサーバー間でメッセージを送受信する「Hello、world」アプリケーションを作成するプロセスについて説明します。WebSocket は、TCP の上の薄くて軽いレイヤーです。これにより、「サブプロトコル」を使用してメッセージを埋め込むのに適しています。このガイドでは、Spring で STOMP [Wikipedia] (英語) メッセージングを使用して、インタラクティブな Web アプリケーションを作成します。STOMP は、下位レベルの WebSocket の上で動作するサブプロトコルです。

構築するもの

ユーザーの名前を伝えるメッセージを受け入れるサーバーを構築します。レスポンスとして、サーバーはクライアントがサブスクライブしているキューにグリーティングをプッシュします。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-messaging-stomp-websocket/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックして、WebSocket を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。

リソース表現クラスを作成する

プロジェクトとビルドシステムのセットアップが完了したため、STOMP メッセージサービスを作成できます。

サービスの相互作用について考えることでプロセスを開始します。

サービスは、本文が JSON オブジェクトである STOMP メッセージに名前を含むメッセージを受け入れます。名前が Fred の場合、メッセージは次のようになります。

{
    "name": "Fred"
}

名前を伝えるメッセージをモデル化するために、次のリスト(src/main/java/com/example/messagingstompwebsocket/HelloMessage.java から)が示すように、name プロパティと対応する getName() メソッドを持つプレーンな古い 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!"
}

挨拶表現をモデル化するには、次のリスト(src/main/java/com/example/messagingstompwebsocket/Greeting.java から)に示すように、content プロパティと対応する getContent() メソッドを持つ別のプレーンな古い 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 メッセージを受信して挨拶メッセージを送信するコントローラーを作成します。

メッセージ処理コントローラーを作成する

STOMP メッセージングを操作する Spring のアプローチでは、STOMP メッセージを @Controller (Javadoc) クラスにルーティングできます。例: 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 (Javadoc) アノテーションは、メッセージが /hello 宛先に送信された場合、greeting() メソッドが呼び出されることを保証します。

メッセージのペイロードは HelloMessage オブジェクトにバインドされ、greeting() に渡されます。

内部的に、メソッドの実装は、スレッドを 1 秒間スリープさせることにより、処理の遅延をシミュレートします。これは、クライアントがメッセージを送信した後、サーバーが非同期にメッセージを処理する必要がある限り、処理できることを示すためです。クライアントは、レスポンスを待たずに必要な作業を続行できます。

1 秒の遅延の後、greeting() メソッドは Greeting オブジェクトを作成して返します。戻り値は、@SendTo (Javadoc) アノテーションで指定されている /topic/greetings のすべてのサブスクライバーにブロードキャストされます。入力メッセージからの名前はサニタイズされることに注意してください。この場合、クライアント側のブラウザー 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 (Javadoc) でアノテーションが付けられています。名前が示すように、@EnableWebSocketMessageBroker は WebSocket メッセージ処理を有効にし、メッセージブローカーによって支援されます。

configureMessageBroker() メソッドは、WebSocketMessageBrokerConfigurer のデフォルトメソッドを実装して、メッセージブローカーを構成します。まず、enableSimpleBroker() を呼び出して、単純なメモリベースのメッセージブローカーが、/topic をプレフィックスとして持つ宛先でクライアントにグリーティングメッセージを戻すことができるようにします。また、@MessageMapping アノテーションが付けられたメソッドにバインドされているメッセージの /app プレフィックスも指定します。このプレフィックスは、すべてのメッセージマッピングを定義するために使用されます。例: /app/hello は、GreetingController.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" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" 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 ファイルは、WebSocket 経由で STOMP 経由でサーバーと通信するために使用される StompJS (英語) JavaScript ライブラリをインポートします。また、クライアントアプリケーションのロジックを含む app.js もインポートします。次のリスト ( src/main/resources/static/app.js から) は、そのファイルを示しています。

const stompClient = new StompJs.Client({
    brokerURL: 'ws://localhost: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.onConnect および sendName 関数です。

stompClient は、WebSocket サーバーが接続を待機するパス /gs-guide-websocket を参照する brokerURL で初期化されます。接続が成功すると、クライアントは /topic/greetings 宛先にサブスクライブし、サーバーはそこにグリーティングメッセージを発行します。その宛先で挨拶が受信されると、挨拶メッセージを表示するために段落要素が DOM に追加されます。

sendName() 関数は、ユーザーが入力した名前を取得し、STOMP クライアントを使用して /app/hello 宛先(GreetingController.greeting() が受信する)に送信します。

main.css は、必要に応じて省略できます。または、<link> を解決できるように、空の main.css を作成することもできます。

アプリケーションを実行可能にする

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: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? 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 ファイルを作成することもできます。

ロギング出力が表示されます。サービスは数秒以内に起動して実行されるはずです。

サービスをテストする

サービスが実行されたため、ブラウザーで http://localhost:8080 をポイントし、接続ボタンをクリックします。

接続を開くと、名前を尋ねられます。名前を入力して送信をクリックします。あなたの名前は、STOMP 経由で JSON メッセージとしてサーバーに送信されます。1 秒のシミュレートされた遅延の後、サーバーはページに表示される "Hello" グリーティングと共にメッセージを送り返します。この時点で、別の名前を送信するか、切断ボタンをクリックして接続を閉じることができます。

要約

おめでとう! Spring を使用して STOMP ベースのメッセージングサービスを開発しました。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。

コードを入手する