Glassfish4とHTML間でJSON形式のデータをWebsocketにてやり取りしてみたのでメモ。
大まかな動きは以下
- HTMLからJSONで入力テキストと現在日時を送信。
- GlassfishのWebsocketで受信(&Decode)
- 受け取ったデータに情報(とりあえずユーザ情報代わりのセッションID)を付与し繋がっているクライアント(HTML)全体にJSON形式で返信
- HTMLで受け取ったデータを表示
(html->glassfishのデータ形式)
{"message":"メッセージ","messageTime":時間(ミリ秒)}
(glassfish->htmlのデータ形式)
{"message":"メッセージ","sessionId":"セッションID","messageTime":"HH:mm:ss"}
斜体は変数
Maven
必要な依存関係を追加。(動作確認はeclipse上のM2E)
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<!-- 8/10追記 javaee-web-apiの中に入っていたもので足りた -->
<!-- Websocket (JSR-356) -->
<dependency>
<groupId>javax.net.websocket</groupId>
<artifactId>javax.net.websocket-api</artifactId>
<version>1.0-b06</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.glassfish.websocket</groupId>
<artifactId>websocket-api</artifactId>
<version>0.2</version>
<scope>provided</scope>
</dependency>
<!-- JSON Processing (JSR-353) を使う場合-->
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.0.2 </version>
</dependency>
</dependencies>
画面
- new WebSocket(arg)のargで接続先指定をしてWebSocketオブジェクトを取得する
- 接続先は@ServerEndpointをつけたサーバサイドのPOJOに指定したもの。
- WebSocketのsendで送信、onmessageで受信
- Websocketで送られてきたデータはsocket.onmessageで渡される引数(今回はmessage)のdataに入っている。
- 取り出してからの扱いは普通のJSON。
<!DOCTYPE html>
<html>
<head>
<title>JsonのWebSocketテスト</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script type="text/javascript">
var socket;
$(document).ready(
function() {
var host = "ws://localhost:8080/first/hellojson/";
socket = new WebSocket(host);
socket.onmessage = function(message) {
try {
var wsRes = $.parseJSON(message.data);
$('#log').append(
wsRes.messageTime +
"(送信者=" + wsRes.sessionId + "):" +
wsRes.message + "<br/>");
} catch (e) {
alert(e);
return;
}
}
$('#send').click(function() {
var obj = new Object();
obj.message = $('#msg').val();
obj.messageTime = new Date().getTime();
var jsonString = JSON.stringify(obj);
socket.send(jsonString);
$('#msg').val('');
})
});
</script>
</head>
<body>
<h1>JsonのWebSocketテスト</h1>
<div id="log"></div>
<input id="msg" type="text" />
<button id="send">送信</button>
</body>
</html>
WebSocket用のPOJO(GlassFish側)
- @ServerEndpointをつけたPOJOでWebSocket用クラス(サーバエンドポイント)を作る
- パスはvalueで指定。自分の環境ではコンテキストがfirstなのでfirst/hellojson/がサーバエンドポイントのアドレス
- @onMessageが繋がっている(クライアントサイドの)エンドポイントから呼ばれたときに動く個所
- decodersでクラスを指定すると、やってきたメッセージが指定されたクラスでデコードされて渡される。
- encodersでクラスを指定すると、指定したクラスでエンコードしたデータを、繋がっているエンドポイントに渡せる。
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import jp.co.epea.first.json.TestData;
import jp.co.epea.first.json.TestDecoder;
import jp.co.epea.first.json.TestEncoder;
@ServerEndpoint(value = "/hellojson/",
decoders = { TestDecoder.class }, encoders = { TestEncoder.class })
public class HelloJson {
static Set<Session> sessions = Collections
.synchronizedSet(new HashSet<Session>());
@OnMessage
public void onMessage(TestData data, Session sess) {
System.out.println("Call HelloJson:data[" + data + "]");
data.setSessionId(sess.getId());
for (Session s : sessions) {
s.getAsyncRemote().sendObject(data);
}
}
@OnOpen
public void open(Session sess) {
System.out.println("開始します:" + sess.getId());
sessions.add(sess);
}
@OnClose
public void close(Session sess) {
System.out.println("終了します:" + sess.getId());
sessions.remove(sess);
}
@OnError
public void error(Session sess, Throwable t) {
System.out.println("エラーです:" + sess.getId());
t.printStackTrace();
}
}
decodersのクラス
- javax.websocket.DecoderのText<T>をimplementsする。(Text形式であるJSONの場合)
- <T>に入るのはエンドポイントのOnMessageに渡したい型
- willDecodeメソッドがデコードできるかどうか判定。
- バリデーションとかはwillDecodeでなくOnMessageでやるべきがどうかは未調査。(未実装)
- decodeメソッドで返却するオブジェクトを生成しreturnする。
- JSON Processingを使っているが何を使ってObject化してもOK
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
public class TestDecoder implements Decoder.Text<TestData> {
@Override
public void destroy() {
System.out.println("TestDecoder#destroy");
}
@Override
public void init(EndpointConfig arg0) {
System.out.println("TestDecoder#init");
}
@Override
public TestData decode(String inputString) throws DecodeException {
JsonObject jsonObject = Json.createReader(new StringReader(inputString)).readObject();
return new TestData(jsonObject);
}
/*
* Answer whether the given String can be decoded into an object of type
* T.だそう 入力チェックもここでやるのがよい?
*/
@Override
public boolean willDecode(String inputString) {
try {
Json.createReader(new StringReader(inputString)).readObject();
return true;
} catch (JsonException ex) {
ex.printStackTrace();
return false;
}
}
}
encodersのクラス
- javax.websocket.EncoderのText<T>をimplementsする。(Text形式であるJSONの場合)
- <T>に入るのはエンドポイントのOnMessageで呼ぶsendObjectの引数(返す元ネタ)
- encodeメソッドで返却するStringを生成しreturnする。
- JSON Processingを使っているが何を使ってString化してもOK
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.json.Json;
import javax.json.JsonObject;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
public class TestEncoder implements Encoder.Text {
@Override
public void init(EndpointConfig paramEndpointConfig) {
System.out.println("TestEncoder#init");
}
@Override
public void destroy() {
System.out.println("TestEncoder#destroy");
}
@Override
public String encode(TestData paramData) throws EncodeException {
JsonObject model = Json.createObjectBuilder()
.add("message", paramData.getMessage())
.add("sessionId",paramData.getSessionId())
.add("messageTime", formatDate(paramData.getMessageTime()))
.build();
return model.toString();
}
private String formatDate(long millDate){
return new SimpleDateFormat("HH:mm:ss").format(new Date(millDate));
}
}
データクラス
import javax.json.JsonObject;
public class TestData {
// メッセージ
private String message;
// 送信日時
private long messageTime;
// sessionId(ユーザ名の変わり)
private String sessionId;
public TestData(JsonObject jsonObject) {
if(jsonObject.containsKey("message")){
this.message = jsonObject.getString("message");
}
if(jsonObject.containsKey("messageTime")){
this.messageTime = jsonObject.getJsonNumber("messageTime").longValue();
}
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
String getMessage() {
return message;
}
long getMessageTime() {
return messageTime;
}
String getSessionId() {
return sessionId;
}
}
github[https://github.com/epea/test01/tree/WS_JSON]