先月末に京都の伏見に引っ越しました。
会社の固定回線もまだ引けていなく、PC一台を梱包開いて無線でつなげているようなまだまだな状態です。
京都は普通に歩いていても、竜馬が切られて隠れていた材木置き場所在地とか、佐久間象山が切られた所在地とか、八重の桜でタイムリーな幕末の史跡が見つかりますね。引っ越ししたばっかりで、交通手段が徒歩(&電車)の速度なので文明開化したら色々見えなくなるのかもしれませんが。
先月末に京都の伏見に引っ越しました。
会社の固定回線もまだ引けていなく、PC一台を梱包開いて無線でつなげているようなまだまだな状態です。
京都は普通に歩いていても、竜馬が切られて隠れていた材木置き場所在地とか、佐久間象山が切られた所在地とか、八重の桜でタイムリーな幕末の史跡が見つかりますね。引っ越ししたばっかりで、交通手段が徒歩(&電車)の速度なので文明開化したら色々見えなくなるのかもしれませんが。
昨日15日に大田区の蒲田にオープンしたクライミング(ボルダリング)ジムのKrimp(クリンプ)行ってきました。
ホールドまだかなり付けていないのがあったのであくまで現状です。
都心勤務で京浜東北線で横浜方面に帰るのだとかなりいい感じだと思う。
twitterを眺めていたら(元のツイートながれて見つからなくなった。。。)スノーデン氏が自殺体で見つかったという記事へのリンクが。
http://www.chronicle.su/politics/snowden-dead-of-apparent-suicide/
嫁との感想。
あめりかこえ~
元ネタが半分ネタサイトらしいけど、それでもほんと思うわ
先ほどに続いて送受信部分
エコープログラムなのでServerEndpointで受信されたデータは(若干加工され)ClientEndpointに戻される。そのため、続けて受信処理が走る。
(コントローラーの関連する部分)
public class SingleController implements Initializable { @FXML private void handleSendAction(ActionEvent event) { System.out.println("SingleController#handleSendAction"); wsClient.send(msgInput.getText(), sess); } }
(ClientEndpointの関連する部分)
@ClientEndpoint( decoders = { ClientDecoder.class }, encoders = { ClientEncoder.class }) public class WSJsonSingleClient { @OnMessage public void onMessage(ClientData dataObj) { System.out.println("WSJsonSingleClient#onMessage"); final ClientData channeled = dataObj; Platform.runLater(new Runnable() { @Override public void run() { viewObj.write( channeled.getSessionId(), channeled.getMessageTimeString(), channeled.getMessage()); }; }); } public void send(String text,Session sess) { System.out.println("WSJsonSingleClient#send"); ClientData dataObj = new ClientData(text); sess.getAsyncRemote().sendObject(dataObj); } }
(ClientEncoder)
public class ClientEncoder implements Encoder.Text<ClientData> { @Override public void init(EndpointConfig paramEndpointConfig) { System.out.println("ClientEncoder#init"); } @Override public void destroy() { System.out.println("ClientEncoder#destroy"); } @Override public String encode(ClientData paramData) throws EncodeException { System.out.println("ClientEncoder#encode"); JsonObject model = Json.createObjectBuilder() .add("message", paramData.getMessage()) .add("messageTime",paramData.getMessageTime()) .build(); return model.toString(); } }
(ClientDecoder)
public class ClientDecoder implements Decoder.Text<ClientData> { @Override public void destroy() { System.out.println("ClientDecoder#destroy"); } @Override public void init(EndpointConfig arg0) { System.out.println("ClientDecoder#init"); } @Override public ClientData decode(String inputString) throws DecodeException { System.out.println("ClientDecoder#decode"); try{ JsonObject jsonObject = Json.createReader(new StringReader(inputString)).readObject(); return new ClientData(jsonObject); } catch(Exception e){ e.printStackTrace(); throw new DecodeException(inputString,"ClientDecoder#decode失敗", (Throwable)e); } } @Override public boolean willDecode(String inputString) { try { System.out.println("ClientDecoder#willDecode"); Json.createReader(new StringReader(inputString)).readObject(); return true; } catch (JsonException ex) { ex.printStackTrace(); return false; } } }
github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE
先ほどの続き
(コントローラーの関連する部分)
public class SingleController implements Initializable { private WSJsonSingleClient wsClient = null; private Session sess = null; // 略 @FXML private void handleStartButtonAction(ActionEvent event) { System.out.println("SingleController#handleStartButtonAction"); try { ClientManager m = org.glassfish.tyrus.client.ClientManager .createClient(); URI clientURI = new URI("ws://localhost:8080/first/hellojson/"); sess = m.connectToServer(wsClient, clientURI); } catch (DeploymentException | URISyntaxException e) { Logger.getLogger(SampleController.class.getName()).log( Level.SEVERE, null, e); } } @FXML private void handleStoptButtonAction(ActionEvent event) { System.out.println("SingleController#handleStoptButtonAction"); try { sess.close(); } catch (IOException e) { e.printStackTrace(); } } // 略 }
(ClientEndpointの関連する部分)
@ClientEndpoint( decoders = { ClientDecoder.class }, encoders = { ClientEncoder.class }) public class WSJsonSingleClient { // 略 @OnOpen public void onOpen(Session session) { System.out.println("WSJsonSingleClient#onOpen"); Platform.runLater(new Runnable() { @Override public void run() { viewObj.open(); }; }); } // 略 @OnClose public void closeConnection(Session session) { System.out.println("WSJsonSingleClient#closeConnection"); Platform.runLater(new Runnable() { @Override public void run() { viewObj.close(); }; }); } // 略 }
github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE
先ほどの続き
※1 こちらにあるようにコントロールに結びついたObservableListを別スレッドから操作した場合は、その操作が画面に反映される。ラベルとかボタンとかのコントロールで試した範囲ではJavaFXのスレッドかどうかチェックにかかってうまくいかなかったがやりようはあるかもしれない。
Applicationクラスは起動するだけ。
public class WSJsonSingle extends Application { @Override public void start(Stage stage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("SingleJson.fxml")); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
FXMLはfx:idふって、onActionを紐付けている程度。
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane prefHeight="363.0" prefWidth="502.0" xmlns:fx="http://javafx.com/fxml" fx:controller="jp.co.epea.wsclient.SingleController"> <children> <Label alignment="CENTER" contentDisplay="CENTER" prefHeight="43.0" prefWidth="502.0" text="JSON送受信" /> <VBox layoutY="43.0" prefHeight="331.0" prefWidth="502.0"> <children> <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0"> <children> <Button fx:id="startButton" mnemonicParsing="false" onAction="#handleStartButtonAction" text="開始" /> <Button fx:id="stopButton" mnemonicParsing="false" onAction="#handleStoptButtonAction" text="終了" /> <Label fx:id="statusLabel" text="ステータス" /> </children> </HBox> <HBox prefHeight="100.0" prefWidth="200.0"> <children> <TextField fx:id="msgInput" editable="false" prefWidth="200.0" /> <Button fx:id="sendButton" mnemonicParsing="false" onAction="#handleSendAction" text="送信" /> </children> </HBox> <VBox prefHeight="200.0" prefWidth="100.0"> <children> <HBox prefHeight="100.0" prefWidth="200.0"> <children> <Label text="発言者:" /> <Label fx:id="nameLabel" text="発言者初期値(起動時に変更)" /> </children> </HBox> <HBox prefHeight="100.0" prefWidth="200.0"> <children> <Label text="発言日時:" /> <Label fx:id="timeLabel" text="発言日時初期値(起動時に変更)" /> </children> </HBox> <HBox prefHeight="100.0" prefWidth="200.0"> <children> <Label text="発言内容:" /> <Label fx:id="msgLabel" text="発言内容初期値(起動時に変更)" /> </children> </HBox> </children> </VBox> </children> </VBox> </children> </AnchorPane>
WSJsonSingle#mainから。
画面項目の初期化と、ClientEndpointのインスタンス化(まだ接続しない)。
public class SingleController implements Initializable {
private WSJsonSingleClient wsClient = null;
@Override
public void initialize(URL location, ResourceBundle resources) {
// コントロールをまとめたクラスを作って
ViewObj obj = new ViewObj(nameLabel, timeLabel, msgLabel, statusLabel,
msgInput, startButton, stopButton, sendButton);
// ClientEndpointに渡してインスタンス化
wsClient = new WSJsonSingleClient(obj);
}
}
public class ViewObj {
private Label nameLabel;
// 略
ViewObj(Label nameLabel, Label timeLabel, Label msgLabel,
Label statusLabel, TextField msgInput, Button startButton,
Button stopButton, Button sendButton) {
super();
// 略
// コントロール類の初期化(項目設定とか、ボタンのdisable周りとか)
this.init();
}
private void init(){
nameLabel.setText("");
// 略
}
}
@ClientEndpoint( decoders = { ClientDecoder.class }, encoders = { ClientEncoder.class })
public class WSJsonSingleClient {
private ViewObj viewObj;
public WSJsonSingleClient(ViewObj viewObj) {
this.viewObj = viewObj;
}
// 略
}
github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE
Glassfish4とJavaFX間でJSON形式のデータをWebsocketにてやり取りしてみたのでメモ。
何ページかに分かれているけど実際にJSONのハンドリングをしている箇所はこちら
サーバサイドはこの前HTMLとやり取りしたものとほぼ同じ。
大まかな動きは以下。なお、表示はリスト形式ではなくてラベルの中身を置換していく形式(表示以外はやっていることはHTMLの時とほぼ一緒)
データ形式はこの前と同じ。(なので、クライアントはこの前のHTMLとJavaFXで併用できる。)
(JavaFX->glassfishのデータ形式) {"message":"メッセージ","messageTime":時間(ミリ秒)} (glassfish->JavaFXのデータ形式) {"message":"メッセージ","sessionId":"セッションID","messageTime":"HH:mm:ss"} 斜体は変数
必要な依存関係を追加。(動作確認はeclipse上のM2E)
<dependencies> <!-- JavaFX 2 --> <dependency> <groupId>com.oracle</groupId> <artifactId>javafx</artifactId> <version>2.2</version> <scope>system</scope> <systemPath>${javafx2.home}</systemPath> </dependency> <!-- Websocket (JSR-356) --> <!-- TYRUS-210の対応で1.2にあげている --> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-client</artifactId> <version>1.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-container-grizzly</artifactId> <version>1.2</version> <scope>compile</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>
開始ボタンしか押せない状態。裏ではWebSocket用のClientEndPointをインスタンス化している。
入力欄や送信ボタン・終了ボタンが使えるようになる。
入力メッセージと時間がサーバに送信される。そしてデータを受信したサーバにて加工(といってもセッションID文字列を付加しただけ)されたデータがクライアントに送られ、そのデータがラベルに表示される。
開始ボタンしか押せない状態になる。
github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE
昨日書いたDecoder.TextのwillDecodeが2度呼ばれる問題の対応(下参照)、試したら動いた。
Tyrus 1.2は既にGlassFishに統合・組み込まれています。 Nightly Buildのダウンロードもしくは手作業で新しいTyrusにアップグレードすることができます (手作業の場合、全てのTyrusのjarファイルを置き換えて下さい)。
以上でeclipseから動かしてみるとDecoder.TextのwillDecodeが2度呼ばれることは無くなっていた。Eclipse上でのMaven指定等は特に修正する必要はなかった。
他にもいくつかバグが修正されているのでそれも直っているはず。
WebSocketのServerEndpointとClientEndpointでDecoder.Text<T>を使っていたらwillDecodeが2度呼ばれていた。
Tyrusのバグ(TYRUS-210)とのこと。(1.2で修正済み)
(ServerEndpoint) 情報: TestDecoder#init 情報: TestDecoder#willDecode 情報: TestDecoder#willDecode 情報: TestDecoder#decode (ClientEndpoint) ClientDecoder#init ClientDecoder#willDecode ClientDecoder#willDecode ClientDecoder#decode WSJsonSingleClient#onMessage
クライアントサイドは新しいバージョンをpomに指定して修正されたことを確認。
<dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-client</artifactId> <version>1.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-container-grizzly</artifactId> <version>1.2</version> <scope>compile</scope> </dependency> ClientDecoder#init ClientDecoder#willDecode ClientDecoder#decode WSJsonSingleClient#onMessage
サーバサイドのGlassfishも下のように対応されているとのこと。(参照元)
Tyrus 1.2は既にGlassFishに統合・組み込まれています。 Nightly Buildのダウンロードもしくは手作業で新しいTyrusにアップグレードすることができます (手作業の場合、全てのTyrusのjarファイルを置き換えて下さい)。
サーバサイドのMavenで対応されているものはぱっとみ見つからなかった。->実装の修正なのでサーバランタイムを変えたらいい? -> 変えたら治った
先日書いたWebsocketでJSONデータをやり取りしたブログだけど、エラー出力をきちんとしていなくてはまった。
Decoder.Text<T>のdecodeメソッド内でExceptionが発生した場合に、明示的にログ・スタックトレースの出力をしておかないと特にスタックトレース等が出力されないでdecodeメソッド(とそれから呼ばれる@OnMessage)が終了してしまう。(コンソールにエラーがつらつら出るようなことは無い。)
そのため、下みたいな感じで明示的にエラーあったことを出力してやらないとわかりにくい。
@Override public TestData decode(String inputString) throws DecodeException { try{ JsonObject jsonObject = Json.createReader(new StringReader(inputString)).readObject(); return new TestData(jsonObject); } catch(Exception e){ DecodeException de = new DecodeException(inputString,"decode失敗", (Throwable)e); Logger.getLogger(TestDecoder.class.getName()).log( Level.SEVERE, null, de); throw de; } }
Encoder.Text<T>のencodeメソッドも似たような話がある。
@Override public String encode(TestData paramData) throws EncodeException { try{ JsonObject model = Json.createObjectBuilder() .add("message", paramData.getMessage()) .add("sessionId",paramData.getSessionId()) .add("messageTime", formatDate(paramData.getMessageTime())) .build(); return model.toString(); } catch( Exception e ){ EncodeException ee = new EncodeException(paramData, "encode失敗", (Throwable)e); Logger.getLogger(TestEncoder.class.getName()).log( Level.SEVERE, null, ee); throw ee; } }
一応decodeでエラーを発生させたときのログサンプル
重大: javax.websocket.DecodeException: decode失敗 at jp.co.epea.first.json.TestDecoder.decode(TestDecoder.java:33) at jp.co.epea.first.json.TestDecoder.decode(TestDecoder.java:1) at org.glassfish.tyrus.core.EndpointWrapper.decodeCompleteMessage(EndpointWrapper.java:278) at org.glassfish.tyrus.core.SessionImpl.notifyMessageHandlers(SessionImpl.java:386) at org.glassfish.tyrus.core.EndpointWrapper.onMessage(EndpointWrapper.java:495) at org.glassfish.tyrus.server.TyrusEndpoint.onMessage(TyrusEndpoint.java:174) at org.glassfish.tyrus.websockets.DefaultWebSocket.onMessage(DefaultWebSocket.java:156) at org.glassfish.tyrus.websockets.frametypes.TextFrameType.respond(TextFrameType.java:66) at org.glassfish.tyrus.websockets.DataFrame.respond(DataFrame.java:102) at org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler.onDataAvailable(TyrusHttpUpgradeHandler.java:113) at org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.processDataAvailable(InputBuffer.java:488) at org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.onDataAvailable(InputBuffer.java:453) at org.glassfish.grizzly.http.io.InputBuffer.append(InputBuffer.java:855) at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:222) at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119) at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288) at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206) at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136) at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114) at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77) at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838) at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113) at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115) at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55) at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135) at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564) at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544) at java.lang.Thread.run(Thread.java:724) Caused by: java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to javax.json.JsonNumber at org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImpl.getJsonNumber(JsonObjectBuilderImpl.java:171) at jp.co.epea.first.json.TestData.<init>(TestData.java:21) at jp.co.epea.first.json.TestDecoder.decode(TestDecoder.java:30) ... 27 more