💬
SpringBoot(+Gradle)でWebSocketによるチャットアプリを作成する
基本的に下記記事を参考にさせていただいています。[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経由で公開する記事を書きたいと思います。
Discussion