Javaでwebsocketを試してみる


rubyのwebsocketはだいたい分かった。じゃぁJavaで組み込むにはどうしたもんか、と。

ruby関係は古橋さんの日記でほぼ分かります。 
 古橋貞之の日記: http://d.hatena.ne.jp/viver/

Javaでwebsocketを試すときに考えること

  • webサーバは?
  • servletはどう使うのか
  • ライブラリは?
  • websocketの実装状況は?

このくらいかな。

ミドルウェアまわり

Webサーバ、rubyの場合はthinをそのまま利用したものの、Javaの場合はやっぱapacheでアプリサーバにtomcatだろうか。調べたところ、Jettyで決まりで。WebSocketに対応しているとのことなので。クライアントとの親和性もchrome,safariなら問題なさそう。問題ないってのは、想定している実装があっているかということだが、chromesafariもwebsocketの仕様draft 75,76の実装で動くことはrubyで確認できたので、そこはJettyも対応しているということ。ちなみにJettyはver8ではもっと先に仕様にも対応していそう。
なので、サーブレットコンテナにJettyを使って、SSLならapacheを組み合わせればよいかと。

どういう構造になる?

Websocketはhttpのリクエストをハンドシェイクしてwebsocketの通信に切り替えるので、「サーブレットをGETで呼び出して画面を表示」という概念とどう結びつくのかと思って調べてみた。
Websocketの実装としてjettyのWebSocketインタフェースを実装するカタチでいけるので簡単。

class HogeWebSocket implements WebSocket {
    Outbound outbound;
 
   @Override
    public void onConnect(Outbound outbound) {
        onMessage((byte) 0, "WebSocket is connect.");
    }

    @Override
    public void onMessage(byte frame, byte[] data,int offset, int length) {
    }

    @Override
    public void onMessage(byte frame, String data) {
    }

    @Override
    public void onDisconnect() {
    }
}

これがサーブレットとどういう位置づけになるのかというと、実は何のことはなくて、クライアントからのwebsocketの接続要求を受けて、上のwebsocket実装のインスタンスを生成するだけ。こんな感じ。

public class HogeServlet extends WebSocketServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws javax.servlet.ServletException ,IOException {
        getServletContext().getNamedDispatcher("default").forward(request, response);
    };

    @Override
    protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
       return new HogeWebSocket();
    }

クライアントからws:/hoge:port/でWebSocketの接続要求が来たら、このdoWebSocketConnectが呼ばれる、と。分かり易い。
jettyを起動して、telnetでアクセスすると「WebSocket is connect.」と出たのでまぁよし。

C:\>telnet localhost 8080
GET /hoge/ HTTP/1.1
Origin: http://localhost/
Upgrade: WebSocket
Connection: Upgrade
Host: localhost

telnetで入力するものが見えないのが分かりにくかったけど調べてみると以下のページで解決できた。
Windows telnet コマンドで入力文字を表示する方法 http://blog.glatts.com/blog/imai/?p=203

参考ページ

Jetty7でWebSocket開発 http://labs.mapion.co.jp/blog/java/jetty7websocket.php
WebSocketメモ書き http://d.hatena.ne.jp/prime503/20100802/1280700076

まとめ

「Jetty7のWebSocketをScalaから使う http://d.hatena.ne.jp/yuroyoro/20100316」のサーバ側のまとめが今回の調査にピッタリだったのでそのまま引用させてもらいます(手抜きすぎ)

===引用ここから====
サーバ側の実装は、要点をまとめるとこんな感じです。

  • org.eclipse.jetty.websocket.WebSocketServletを継承したServletを作ります。
  • protected abstract WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) を実装しますよ
  • クライアントからws:/hostname/でWebSocketの接続要求が来たら、このdoWebSocketConnectが呼ばれます。
  • doWebSocketConnectでは、org.eclipse.jetty.websocket.WebSocketを実装したオブジェクトを生成して返すようにします。

WebSocketの実装クラスでは

  • クライアントとの接続が確立したら、void onConnect(WebSocket.Outbound outbound) が呼び出されます。
  • WebSocket.Outboundオブジェクトってのは、クライアントとのSocketみたいなものだとおもっておけばよいです
  • クライアントにメッセージを送るには、WebSocket.OutboundオブジェクトのsendMessage(byte frame, String data) を呼び出せばよいです。
  • クライアントからメッセージを受信したら、void onMessage(byte frame, String data) が呼び出されます。
  • 接続が切られた場合は、void onDisconnect() が呼び出されます。

=========ここまで===

その他

eclipseでjettyの実行時になぜかjavacが見つからない、と怒られた。意味分からないけど、しょうがないので下記ページを参考にして、JAVA_HOMEを環境変数に設定して、tools.jarをビルドパスに設定すると解決した。
EclipseTomcat開発 http://omoshiro-joho.com/tech-center/tips/tips_200410302150.html

こんなエラー。↓

Unable to find a javac compiler;
com.sun.tools.javac.Main is not on the classpath.
Perhaps JAVA_HOME does not point to the JDK