💬

SpringBoot(+Gradle)でWebSocketによるチャットアプリを作成する

2021/06/02に公開

基本的に下記記事を参考にさせていただいています。[1]
SpringBootでWebSocketお試し - Qiita

参考記事と本記事の異なる点は、

  • 参考記事はビルドツールがMavenだが、本記事はGradleを使用している
  • Message受け渡しに用いるObject周りの設計が異なる

あたりになります。

プロジェクト作成

Spring Initializrにアクセスし、DependenciesにWebSocketを追加してプロジェクトの雛形を作成します。

依存ライブラリ追加

build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
+	implementation 'org.webjars:webjars-locator-core'
+	implementation 'org.webjars:sockjs-client:1.0.+'
+	implementation 'org.webjars:stomp-websocket:2.3.+'
+	implementation 'org.webjars:jquery:3.1.+'
+	compileOnly 'org.projectlombok:lombok:1.18.+'
+	annotationProcessor 'org.projectlombok:lombok:1.18.+'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • WebJars+JS/CSSライブラリ
  • Lombok関連

を追加しています。

サーバサイド実装

Model

メッセージは、送受信ともにこのDTOを用いてやりとりされます。
今回は簡単なチャットなので、発言者の名前とメッセージ本文だけを持つクラスにします。

Message.java
import lombok.Data;

@Data
public class Message {

    private String name;
    private String statement;

    public Message(String name, String statement) {
        this.name = name;
        this.statement = statement;
    }
}

Controller

@MessageMappingアノテーションと@SendToアノテーションを用いて、送受信のエンドポイントを定義します。

MessageController.java
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 MessageController {

    @MessageMapping("/message")
    @SendTo("/receive/message")
    public Message send(Message message) throws Exception {
        Thread.sleep(1000);
        return new Message(HtmlUtils.htmlEscape(message.getName()), HtmlUtils.htmlEscape(message.getStatement()));
    }
}

Configuration

WebSocketを利用するためのConfigurationクラスを作成し、エンドポイント定義を行います。

WebSocketConfig.java
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("/receive");
	// 送信エンドポイントの設定(プレフィクス定義)
        config.setApplicationDestinationPrefixes("/send");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 初回WebSocket通信開始時のエンドポイントの設定
        registry.addEndpoint("/websocket").withSockJS();
    }

}

ControllerとConfigurationで行なった定義を合わせて、

  • /websocketでWebSocket通信を開始
  • /send/messageにメッセージを送信
  • /receive/messageでメッセージを受信

というエンドポイント定義になります。
これらをクライアントサイドから利用します。

クライアントサイド実装

最低限の機能だけを実現するためのHTMLとJSの実装です。

index.html
<!DOCTYPE html>
<html>
<head>
  <title>WebSocket Chat</title>
  <script src="/webjars/jquery/jquery.min.js"></script>
  <script src="/webjars/sockjs-client/sockjs.min.js"></script>
  <script src="/webjars/stomp-websocket/stomp.min.js"></script>
  <script src="/app.js"></script>
</head>
<body>

<div class="form-group">
  <label for="connect">WebSocket connection:</label>
  <button id="connect" type="submit">Connect</button>
  <button id="disconnect" type="submit" disabled="disabled">Disconnect
  </button>
</div>
<form class="form-inline">
  <div class="form-group">
    <label for="name">Name</label>
    <input type="text" id="name" placeholder="Your name">
  </div>
  <div class="form-group">
    <label for="name">Message</label>
    <input type="text" id="statement" placeholder="Your Message">
  </div>
  <button id="send" type="submit">Send</button>
</form>
<table>
  <tbody id="message">
  </tbody>
</table>
</body>
</html>
app.js
var stompClient = null;

function setConnected(connected) {
  $("#connect").prop("disabled", connected);
  $("#disconnect").prop("disabled", !connected);
  if (connected) {
    $("#conversation").show();
  } else {
    $("#conversation").hide();
  }
  $("#message").html("");
}

function connect() {
  var socket = new SockJS('/websocket'); // WebSocket通信開始
  stompClient = Stomp.over(socket);
  stompClient.connect({}, function (frame) {
    setConnected(true);
    console.log('Connected: ' + frame);
    // /receive/messageエンドポイントでメッセージを受信し、表示する
    stompClient.subscribe('/receive/message', function (response) {
      showMessage(JSON.parse(response.body));
    });
  });
}

function disconnect() {
  if (stompClient !== null) {
    stompClient.disconnect();
  }
  setConnected(false);
  console.log("Disconnected");
}

function sendMessage() {
  // /send/messageエンドポイントにメッセージを送信する
  stompClient.send("/send/message", {}, JSON.stringify(
      {'name': $("#name").val(), 'statement': $("#statement").val()}));
  $("#statement").val('');
}

function showMessage(message) {
  // 受信したメッセージを整形して表示
  $("#message").append(
      "<tr><td>" + message.name + ": " + message.statement + "</td></tr>");
}

$(function () {
  $("form").on('submit', function (e) {
    e.preventDefault();
  });
  $("#connect").click(function () {
    connect();
  });
  $("#disconnect").click(function () {
    disconnect();
  });
  $("#send").click(function () {
    sendMessage();
  });
});

setTimeout("connect()", 3000);

#connectボタンでWebSocket通信を開始し、#disconnectボタンで切断します。
#nameテキストボックスに名前、#statementテキストボックスに送信したいメッセージの本文を入力し、#sendボタンで送信します。
送受信したメッセージは、#conversationテーブル上に表示されます。

表示されたときのイメージはこんな感じです。

ビルドして実行

GradleでbootRunします。

$ ./gradlew bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.0)
(略)
2021-06-02 21:05:08.540  INFO 22009 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Started.
2021-06-02 21:05:08.547  INFO 22009 --- [           main] c.k.w.WebsocketchatApplication           : Started WebsocketchatApplication in 1.3 seconds (JVM running for 1.531)

ブラウザでlocalhost:8080にアクセスすると、チャットアプリが動作していることを確認できます!

次回はこのアプリをnginx経由で公開する記事を書きたいと思います。

脚注
  1. :公式ドキュメントもあったはずなのですが、今日記事を書くために検索したらなぜかNotFoundになっていました……(2021/06/02時点)
    そのため、記憶の新しいうちに記録を残す目的も兼ねて本記事を作成しています。 ↩︎

Discussion