Dropwizard 0.7.0でWebSocket

Dropwizard 0.7.0では組み込みWebサーバとしてJetty 9.0.7が使われており、Jettyであれば非Java EE7環境でもWebSocketが使えるはずだと思ったので、一番簡単なechoを作って試してみました。

build.gradle

まず、websocket-server への依存関係を追加します。

compile 'org.eclipse.jetty.websocket:websocket-server:9.0.7.v20131107'

これで、推移的な依存関係により、websocket-commonwebsocket-clientwebsocket-servlet が追加されます。

WebSocketクラスの作成

@org.eclipse.jetty.websocket.api.annotations.WebSocket アノテーションをつけたPOJOを作ります。

package example.websocket.echo;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

@WebSocket
public class EchoWebSocket {

    @OnWebSocketConnect
    public void onConnect(Session session) {
        // do nothing
    }

    @OnWebSocketMessage
    public void onText(Session session, String message) {
        session.getRemote().sendStringByFuture(message);
    }

    @OnWebSocketClose
    public void close(Session session, int statusCode, String reason) {
        // do nothing
    }
}

今回のメインロジックですが、これだけです。メッセージを受け取った処理は@OnWebSocketMessageアノテーションをつけたメソッドで実装し、受け取ったメッセージをそのまま同じクライアントに送るようにしています。

ちなみに、このように@WebSocketクラスを作る方法と別の方法として、org.eclipse.jetty.websocket.api.WebSocketListener インタフェースを実装する方法もあるみたいです。

アプリケーションクラスにWebSocketHandlerをセットする

io.dropwizard.Applicationを継承して作ったアプリケーションクラスの run メソッドを修正。

アプリケーションコンテキストにWebSocketHandlerを追加し、先ほど作った EchoWebSocket クラスを登録します。

environment.getApplicationContext().setHandler(new WebSocketHandler() {

    @Override
    public void configure(WebSocketServletFactory factory) {
        factory.register(EchoWebSocket.class);
    }
});

今回一番頭を悩ませたのがこの辺り。最初は WebSocketServlet を作って登録させてみたのですが、うまく動かず、色々試行錯誤した結果、この方法に落ち着きました。

クライアントの作成

JavaScriptでオーソドックスに書きます。

  var ws = new WebSocket("ws://" + location.host + "/");

  ws.onopen = function(event) {
      console.log("connected.");
      ws.send("hello!");
  }

  ws.onmessage = function(event) {
      console.log("message received from server.");
      console.log(event.data);
  }

HTMLでの見せ方は色々(省略)。

一通り書いたらあとはいつも通り ./gradlew jar でビルドしてアプリケーションを起動後、 http://localhost:18080 にアクセスすれば、ブラウザのコンソールログとしてサーバからエコーバックされた "hello!" が出力されると思います。

補足

今回のJettyのバージョンは9.0.7ですが、9.1からは、JettyのWebSocket対応がJSR 356ベースに変わっているみたいです。逆に言うと、Jetty9.0.7のWebSocket対応はJava標準のものではなく、JettyのAPIに依存した形になっています。そのため、今後Dropwizardが対応するJettyのバージョンが9.1以上になれば、ここでの記事の情報は古くなります。

ちなみに最初はJSR 356の参照実装であるTyrusを試したのですが、断念しました。。

参考