日別アーカイブ: 2013年7月20日

JavaFX&Websocket連携(エラーパターン)

対向のサーバが落ちているとき。

素直にハンドシェイクできないという。まぁ、そのまんま。

7 20, 2013 5:43:29 午後 jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1 call
SEVERE: null
javax.websocket.DeploymentException: Handshake response not received.
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:300)
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:172)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:65)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:1)
    at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

 

URI clientURI = new URI(“ws://localhost:8080/first/hellohoge/”);

みたいにURLが間違っている場合。

Handshake errorというやつとスタックとレースにレスポンスコードが返ってくる。

この例だと存在しないパスなので404が返っているけど、WebsocketじゃないただのHTMLだとUpdate Protocolの時に失敗しているので(?)200が返ってくる。

なお、パスの指定時は最後の”/(スラッシュ)”も厳密に見る模様。

7 20, 2013 8:46:51 午後 jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1 call
SEVERE: null
javax.websocket.DeploymentException: Handshake error.
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:285)
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:172)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:65)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:1)
    at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
Caused by: org.glassfish.tyrus.websockets.HandshakeException: Response code was not 101: 404
    at org.glassfish.tyrus.websockets.HandShake.validateServerResponse(HandShake.java:314)
    at org.glassfish.tyrus.websockets.draft06.HandShake06.validateServerResponse(HandShake06.java:98)
    at org.glassfish.tyrus.container.grizzly.WebSocketFilter.handleClientHandShake(WebSocketFilter.java:368)
    at org.glassfish.tyrus.container.grizzly.WebSocketFilter.handleHandshake(WebSocketFilter.java:353)
    at org.glassfish.tyrus.container.grizzly.WebSocketFilter.handleRead(WebSocketFilter.java:274)
    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:837)
    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:565)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
    ... 1 more

Connection had closed.

 

ちなみに、エラーログをはいているのはSampleController#createTaskの中にあるキャッチ節。

        @Override
        protected Task createTask() {
            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    messageLatch = new CountDownLatch(1);
                    try {
                        URI clientURI = new URI("ws://localhost:8080/first/hellohoge/");
                        ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient();
                        cliContainer.connectToServer(new TwitterClient(table), clientURI);
                        messageLatch.await(1, TimeUnit.SECONDS);
                    } catch (DeploymentException | URISyntaxException | InterruptedException ex) {
                        Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    return null;
                }
            };
            return task;
        }

下の@OnErrorは通らずに、@OnClose(前のログだと緑字部分)で終わるので注意。

    @OnClose
    public void closeConnection(Session session) {
        System.out.println("Connection had closed.");
    }

    @OnError
    public void onError(Session session,Throwable t) {
        System.out.println("error");
        t.printStackTrace();
    }

javafx.concurrent.Taskの使い方次第でもっとハンドリングできそうな気がする。
とりあえず参考になりそうなサイト

JavaFXとWebsocketを連携してみる(On Glassfish4.0) その1

サーバサイドはいつものようにGlassfish 4.0。まずは寺田さんのブログを見つつメッセージ送信用のページで入れたメッセージが表示されるようにするとこまで持っていく。(元のブログのようにツイッターを見に行きはしないで、index.htmlで入れたものが表示されるようにする。)

サーバ用のエンドポイント(ws://localhost:8080/first/hello/)

package jp.co.epea.first;

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;

@ServerEndpoint("/hello/")
public class HelloWorld {

    static Set<Session> sessions = Collections
            .synchronizedSet(new HashSet<Session>());

    @OnMessage
    public void onMessage(String message) {
        System.out.println("message[" + message + "]");

        for (Session s : sessions) {
            s.getAsyncRemote().sendText(message);
        }
    }

    @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();
    }
}

メッセージ送信用のページ(http://localhost:8080/first/index.html)

<!DOCTYPE html>
<html>
    <head>
        <title>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/hello/";
                socket = new WebSocket(host);

                socket.onmessage = function(message){
                    $('#log').append(message.data + "<br/>");
                }

                $('#send').click(function(){
                    var text = $('#msg').val();
                    socket.send(text);
                    $('#msg').val('');
                })

            });
        </script>        
    </head>
    <body>
        <h1>WebSocketテスト</h1>
        <div id="log">
        </div>
        <input id="msg" type="text"/>
        <button id="send">送信</button>
    </body>
</html>

 

クライアントサイドの作成の流れは

  1. MavanプロジェクトとしてJavaFXを作成(前回ブログ)
  2. 足りない依存関係を追加
  3. 寺田さんのブログのクライアントサイドプログラムをコピペ
  4. とりあえず動くようになるために最低限の修正を加える

(手順1と3は省略)

2.足りない依存関係を追加

tyrus-clientとtyrus-container-grizzlyはこちらのブログを参考にしたときに「Grizzly 関連の jar」の今版として使えそうなので試したところいけたっぽいので持ってきた。(ユーザガイド)

    <dependencies>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>javafx</artifactId>
            <version>2.0</version>
            <scope>system</scope>
            <systemPath>C:\dev\pleiades43\java\7\lib/jfxrt.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.glassfish.tyrus</groupId>
            <artifactId>tyrus-client</artifactId>
            <version>1.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.tyrus</groupId>
            <artifactId>tyrus-container-grizzly</artifactId>
            <version>1.1</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

 

4.とりあえず動くようになるために最低限の修正を加える

TwitterClientのアノテーションを@ClientEndpoint、@OnOpen、@OnMessage、@OnCloseに変更。

SampleController.javaのURLを修正。

ClientEndpointConfigurationの行をコメントアウト。

URI clientURI = new URI("ws://localhost:8080/first/hello/");
//ClientEndpointConfiguration clientConfig = new DefaultClientConfiguration();

 

動作確認

サーバを起動する。

JavaFXを起動する。

a

start timelineボタンをクリックしてサーバと接続

(コンソールメッセージ)
Connection had opened.

 

index.htmlからメッセージを送信

b

 

JavaFX側に表示される

c

 

一応これで動作しているようなのでこいつを基点に触っていくことにする。

(JavaFX側をXボタンで閉じたときにOncloseが呼ばれてないっぽいけど、実装的にそういう動きになるのか環境がおかしいのか。。。。。)

2013/11/03追記

子の実装ではJavaFXのTableViewを使用することで、WebSocket側のスレッドによる変更がJavaFXのスレッドに通知されている。(オブザーバ)

桜庭さんに伺ったところ、「(GUIはそっちのスレッドから動かすのが基本なので)きちんと動かないことあるかもよ」とのことでした。

他の日記ではLabelで同期しようとした場合にPlatform.runLaterでJavaFXのスレッドに処理を戻しているけど、そちらの方法が基本とのことでした。