ページ

2015年3月30日月曜日

Play2で簡単WebSocket

今回はPlay FrameworkでWebSocketに挑戦します。今回準備するのは以下の二つ。
  • Play Framework 2.3
  • jQuery 2.1.3
Play2は以前使ったのでこちらを参照ください。
jQueryは公式サイトからダウンロードしましょう。ダウンロードしたファイルはプロジェクト配下の \public\javascripts にコピーします。

さて今回はできるだけシンプルなサンプルを心がけました。特に何かの役に立つものではないですが、仕組みの理解を優先したつもりです(が分かりにくかったらゴメンなさい・・・)。

まずはアクセス方法から。

--------------------------------------------------
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index()
GET     /message                    controllers.Application.message()

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)
--------------------------------------------------

/messageを追加しました。これで"ws://localhost:9000/message"でWebSocket接続が可能になります。(localhostの部分は適当に読み替えてください。)

次にindex.scala.htmlです。

--------------------------------------------------
@(message: String)

@main("WebSocket Sample") {

    <p>@message</p>
    <div id="text"></div>
    <p></p>
    <input type="button" value="Start" onClick="start()">
    <input type="button" value="Stop" onClick="stop()"><p>
    <form name="form">
    <input type="text" name="message" value="サンプルテキスト">
    <input type="button" value="Send" onClick="send()">
    </form>

}
--------------------------------------------------

今回はStartボタンでWebSocket接続を行い、Sendボタンでテキストボックスの文字列をサーバに送信、StopボタンでWebSocketを切断するといった操作を実現します。

--------------------------------------------------
var connection = null;

function start() {

$("#text").text("");
connection = new WebSocket("ws://localhost:9000/message");
connection.onopen = function() {
log("connected.");
}
connection.onerror = function(event) {
log("error.");
}
connection.onclose = function(event) {
log("closed.");
}
connection.onmessage = function(event) {
log(event.data);
}
}

function stop() {
connection.close();
}

function send() {
connection.send(document.form.message.value);
}

function log(message) {
$("#text").append(message + "<br>");
}
--------------------------------------------------

ここで・・・まだURLをベタ書きしてます・・・。うまく置き換えれるはずなのですが、まだちゃんとやってないです。
ちなみにログ的に動作状況を確認するために文字列はボタンの上部分のdivタグ内にテキストを追加していってます。
最後はコントローラ部分。ここがPlay2でWebSocketするときの肝の部分ですね。

--------------------------------------------------
package controllers;

import play.Logger;
import play.libs.F.Callback;
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.WebSocket;
import views.html.index;

public class Application extends Controller {

    public static Result index() {
        return ok(index.render("WebSocket Sample."));
    }

    public static WebSocket message() {
return new WebSocket() {
      @Override
    public void onReady(final WebSocket.In in, final WebSocket.Out out) {
      Logger.debug("connected.");
      in.onMessage(new Connection(out));
    }
  };
    }

    public static class Connection implements Callback {

     private WebSocket.Out out;

     public Connection(WebSocket.Out out) {
       this.out = out;
     }

     @Override
     public void invoke(String event) {
    Logger.debug(event);
    for (int i=0; i<5; i++) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      out.write(event);
    }
  }
    }
}
--------------------------------------------------

index()で最初の画面を表示します。まー特に変わったところはないです。
message()が問題のWebSocketの接続を受けるメソッドです。index()と異なりWebSocketを返します。このときにWebSocket.Inにその後のメッセージを受信するためのコールバックオブジェクトを登録し、返信用のWebSocket.Outオブジェクトを保存しておきます。
コールバックオブジェクトではメッセージ受信用のハンドラをオーバーライドします。このサンプルでは受け取った文字列を1秒ごとに5回クライアントに返送しています。この部分を実際の処理に書き換えるといろいろ出来そうですね。

あとはもう少しコードをキレイにしたり切断時の処理のことを考えておけばそれなりに使えそうです。いやーそれにしても世の中便利になったもんだ。