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; } }