2017-05-10
http://blog.enjoyxstudy.com/entry/2017/05/10/000000
こないだSTOMP over WebSocketを試してみましたが、今度はSTOMPを使わずに、TextWebSocketHandler
を使ったWebSocketを試してみます。題材も同じくチャットです。
ルームの情報をどのように渡そうか悩みました。コードを書き始めるまでは、WebSocketで接続する先にヘッダとかで渡せばいいかなと思っていましたが、調べてみたら渡せなかったので、やもえずURLのクエリパラメータとして渡すようにしました。
コード
テキストでのやり取りを行うので、TextWebSocketHandler
を利用します。TextWebSocketHandler
の各メソッドをoverrideして必要な処理を実装するだけです。
ルームの情報は、URLのクエリとしてクライアントから送っているので、接続が確立したタイミング(afterConnectionEstablished
)にて、ルーム毎にWebSocketSession
を保持するようにします。 メッセージを受け取ったら、自分のルームと同じWebSocketSession
に対して、メッセージを送るだけです。
package com.example;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Component
public class ChatHandler extends TextWebSocketHandler {
private ConcurrentHashMap<String, Set<WebSocketSession>> roomSessionPool = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String roomName = session.getUri().getQuery();
roomSessionPool.compute(roomName, (key, sessions) -> {
if (sessions == null) {
sessions = new CopyOnWriteArraySet<>();
}
sessions.add(session);
return sessions;
});
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String roomName = session.getUri().getQuery();
for (WebSocketSession roomSession : roomSessionPool.get(roomName)) {
roomSession.sendMessage(message);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String roomName = session.getUri().getQuery();
roomSessionPool.compute(roomName, (key, sessions) -> {
sessions.remove(session);
if (sessions.isEmpty()) {
// 1件もない場合はMapからクリア
sessions = null;
}
return sessions;
});
}
}
WebSocketの設定として、URLとHandlerを紐付けます。
package com.example;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import lombok.AllArgsConstructor;
@Configuration
@EnableWebSocket
@AllArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final ChatHandler chatHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatHandler, "/endpoint");
}
}
クライアント側では、下記のようなコードになりました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>チャット</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css" />
</head>
<body>
<div class="container">
<h2>チャット</h2>
<div class="form-horizontal">
<div class="form-group">
<label for="roomName" class="col-sm-2 control-label">ルーム</label>
<div class="col-sm-2">
<input id="roomName" type="text" class="form-control" value="example" />
</div>
<div class="col-sm-3">
<button id="connectButton" type="button" class="btn btn-default">接続</button>
<button id="disconnectButton" class="btn btn-default">切断</button>
</div>
</div>
<div class="form-group">
<label for="message" class="col-sm-2 control-label">メッセージ</label>
<div class="col-sm-4">
<input id="message" type="text" class="form-control" />
</div>
<div class="col-sm-2">
<button id="sendButton" type="button" class="btn btn-default">送信</button>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-sm-offset-2">
<ul id="messageList" class="list-unstyled">
</ul>
</div>
</div>
</div>
</div>
<script src="/webjars/jquery/1.12.4/jquery.min.js"></script>
<script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script>
$(function() {
var endpoint = 'ws://' + location.host + '/endpoint';
var webSocket = null;
$('#connectButton').click(function() {
$("#messageList").empty();
webSocket = new WebSocket(endpoint + '?' + encodeURIComponent($('#roomName').val()));
webSocket.onopen = function() {
$('#roomName').prop('disabled', true);
$('#connectButton').prop('disabled', true);
$('#disconnectButton').prop('disabled', false);
};
webSocket.onclose = function() {
};
webSocket.onmessage = function(message) {
$('#messageList').prepend($('<li>').text(message.data));
};
webSocket.onerror = function() {
alert('エラーが発生しました。');
};
});
$('#disconnectButton').click(function() {
webSocket.close();
webSocket = null;
$('#roomName').prop('disabled', false);
$('#connectButton').prop('disabled', false);
$('#disconnectButton').prop('disabled', true);
});
$('#sendButton').click(function() {
if (!webSocket) {
alert('未接続です。');
return;
}
webSocket.send($('#message').val());
});
});
</script>
</body>
</html>
STOMPの時のほうが、いろいろシンプルに書けるので、ブラウザがクライアントならば、STOMPを使わない理由はないかなと思っています。