vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

SpringBootでWebSocketを使ってみたい(Sample Backend編)

はじめに

チャットのように、Webサーバーを通してデータの受け渡しをリアルタイムで実施するための技術にWebSocketがあります。

今回はこれをSpringBootから利用する方法として紹介されていた下記の内容について調べつつ、
いじってみたりすることにします。

Keywords

WebSocket

まずWebSocketについてですが、超乱雑にまとめると

  • 最初にクライアント側とサーバー側を連携し、その後は接続・切断をせず小さいオーバーヘッドでやり取りを行う
  • クライアント側からだけでなくサーバー側からもデータ(メッセージ)のやりとりをリクエストできる

といったところでしょうか。
頻繁なメッセージのやりとりが必要なチャットなどを作るときに利用されます。

詳しくは下記のようなページをご覧ください。(内容が間違っていたり、あまりにも説明が不足していることがわかれば後日修正します)

今回のサンプルではsockjsを利用しています。

STOMP

WebSocketでクライアント側とサーバー側でやりとりをするためには、
お互いに共通した方法(プロトコル)を持っている必要があります。

サンプルでは、このためのプロトコルとしてSTOMP(Simple (or Streaming) Text Orientated Messaging Protocol)を使用しています。

正式名称の通り、テキストベースのメッセージでやりとりを行います。

メッセージのやりとりの流れは最初にSubscribeを行い、
更新があればそれを受け取る、ということのようです。

このSTOMPをWebSocket上で使用するため、STOMP.jsを利用しています。
一点気がかりなのは、GitHubを見るとメンテナンスが止まってしまっていること。

みなさんどう対処しているのか(´・ω・`)

。。。とりあえず今回はStomp.jsを使用することとします。

クラス構成

上記のサンプルに含まれるクラスは以下の通りです。

  • src
    • main.java.hello
      • Application.java ・・・ アプリケーションのメインクラス
      • Greeting.java ・・・ Subscriberに送信するメッセージを保持するモデルクラス
      • GreetingController.java ・・・ メッセージのルーティングを行うコントローラークラス
      • HelloMessage.java ・・・ メッセージをクライアント側から受信する時に、メッセージ内容(名前)を格納するモデルクラス
      • WebSocketConfig.java ・・・ エンドポイントの登録、メッセージのやり取りを行うWebSocketMessageBrokerの設定を行う設定クラス

この内「Application.java」はSpringBootの開始を担うメインクラス、「Greeting.java」「HelloMessage.java」はデータを保持するモデルクラスです。

ここからは「GreetingController.java」「WebSocketConfig.java」について追いかけてみます。

GreetingController

コントローラークラスです。

GreetingController.java

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        // 1秒待ってから応答する.
        Thread.sleep(1000);
        // メッセージ内容を"/topic/greetings"のSubscriberに渡す.
        return new Greeting("Hello, " + message.getName() + "!");
    }
}
  • @Controllerアノテーションを付与することで、通常のルーティング処理に加えてメッセージのルーティングも行うことができます。
  • @MessageMappingアノテーションを付与することで、今回は「/hello」にメッセージが送信されたときに関数「greeting(HelloMessage message)」が呼ばれます。
    このときメッセージの内容は、「HelloMessage.java」に格納された形で引数として渡されます。
  • @SendToアノテーションを付与することで、戻り値であるメッセージの内容を「/topic/greetings」をSubscribeしているユーザーに伝えることができます。
  • 「Thread.sleep(1000);」は無くても動作します。

なおマッピングできるパスの形式は、「/hello」の他「/hello.message.*」や「/hello.message.{goodmorning}」といったものも指定できるようです(MessageMapping.classより)。

WebSocketConfig

設定クラスです。

WebSocketConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }
}
  • @EnableWebSocketMessageBrokerアノテーションを付与することで、WebSocketのメッセージのやり取りを有効にします。

ここで実装している内容は2つです。

  1. MessageBrokerの設定
  2. STOMPのエンドポイントの登録

1. MessageBrokerの設定

メッセージのやりとりを仲介するMessageBrokerの設定を行います。

config.enableSimpleBroker(“/topic”);

「/topic」以下のURL(GreetingController.javaのメッセージ送信先である「/topic/greetings」)でメッセージを渡せるようにします。

メッセージを受け取ったら、あらかじめSubscribeしていたユーザーにメッセージが渡されます。

config.setApplicationDestinationPrefixes(“/app”);

「/app」以下のURLにメッセージを送ると、コントローラークラス(GreetingController.java)が呼ばれるようになります。

今回はクライアント側から「/app/hello」にメッセージを送信すると、
GreetingController.javaの「greeting(HelloMessage message)」が呼ばれます。

2. STOMPのエンドポイントの登録

sockjsを使ってWebSocketの接続処理を行うエンドポイントの登録を行います。

「withSockJS()」は今回sockjsを使っているため必要なのですが、
sockjsを使わない場合にWebSocketを使う処理が行えるのか、可能であればどのようにするのかは気になるところです。

AbstractWebSocketMessageBrokerConfigurer

設定クラス(WebSocketConfig.java)で継承しているAbstractWebSocketMessageBrokerConfigurerではどのようなことをしているのか、少し見てみることにしました。

AbstractWebSocketMessageBrokerConfigurer.java

package org.springframework.web.socket.config.annotation;

import java.util.List;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;

public abstract class AbstractWebSocketMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
    }
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
    }
    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
    }
    @Override
    public boolean configureMessageConverters(List messageConverters) {
        return true;
    }
    @Override
    public void addArgumentResolvers(List argumentResolvers) {
    }
    @Override
    public void addReturnValueHandlers(List returnValueHandlers) {
    }
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    }
}

※コメント、Javadocsは省略しています。

WebSocketMessageBrokerConfigurer.javaのメソッドをOverrideしていますが、
中身はすべて空になっています。

ドキュメントを見ると、WebSocketMessageBrokerConfigurer.javaを実装しやすくするため、
オプショナルな関数を空で実装している抽象クラスのようです。

設定クラスで実装していた「configureMessageBroker(MessageBrokerRegistry registry)」も含まれているため、
実装が必須な関数というのは、STOMPのエンドポイントの登録を行う「registerStompEndpoints(StompEndpointRegistry registry)」だけのようですね。

では、WebSocketMessageBrokerConfigurer.javaも見てみます。

WebSocketMessageBrokerConfigurer.java

package org.springframework.web.socket.config.annotation;

import java.util.List;

import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;

public interface WebSocketMessageBrokerConfigurer {

    void registerStompEndpoints(StompEndpointRegistry registry);

    void configureWebSocketTransport(WebSocketTransportRegistration registry);

    void configureClientInboundChannel(ChannelRegistration registration);

    void configureClientOutboundChannel(ChannelRegistration registration);

    void addArgumentResolvers(List argumentResolvers);

    void addReturnValueHandlers(List returnValueHandlers);

    boolean configureMessageConverters(List messageConverters);

    void configureMessageBroker(MessageBrokerRegistry registry);
}

※コメント、Javadocsは省略しています。

オプショナルな関数の内容は下記を参照していただきたいところですが、
WebSocketでのメッセージの送受信を行う関数(MessageChannel)の設定などが行えるため、必要に応じてOverrideすることになりそうです。

なお、interfaceで共通の関数を指定して、共通の処理を抽象クラスで実装し、個別の処理を抽象クラスを継承したクラスで実装する、
という流れはまさにJava本格入門で読んだ内容だな〜、などと思いながら見ていました。

おわりに

細かく追ってはみたつもりですが、全体的にまだフワフワしているため、
実際にサンプルを作りながらもう少し突っ込んで触る必要がありそうです。

次回はクライアント側を追いかけてみます。

参照

Spring

WebSocket

STOMP