2011年8月6日土曜日

Play framework + WebSocket + Growlで遊ぶ

第1回「Play framework勉強会」IN 関西@tan_go238さんが、NettyとGrowlを組み合わせて、指定ポートのソケットに書き込まれたメッセージをGrowlに飛ばすアプリを披露されていました。
それにインスパイアされて、Play + WebSocket + Growlを組あわせて、HTTPやWebSocket経由で送信されたメッセージをサーバのGrowlや、他のWebSocket接続ユーザへ飛ばすアプリを書いてみました。

ソースコードはこちらで公開しています。
本文には特に重要なコードしか掲載していませんので、ざっと記事を読んでいただいたあと、全体のコードを眺めて復習してみてください。
mumoshu/play-websocket-growl-sample at master - GitHub

GrowlやWebSocketでメッセージを送信する


public class Notifier {
    /* 略 */

    public static void notify(String message) {
        Logger.info("Notify: %s", message);
        growl().notify("system", "Play feat. Growl", message);
        NotificationStream.publish(message);
    }
}

HTTPやWebSocketから送信されたメッセージは、Notifierというクラス経由で他のWebSocketクライアントやサーバのGrowlへ飛ばします。
このコードでは、
growl().notify(...)
でサーバへGrowlによる通知を送信しつつ、
NotificationStream.publish(message)
により接続中のWebSocketクライアントへメッセージを送信します。

これを、HTTP POSTされたときやWebSocketクライアントからのメッセージを受信した際に呼び出します。

HTTP経由


HTTPやWebSocketからはそれぞれこんな感じで受け取ったメッセージをNotifierに渡します。

public class Application extends Controller {
 
    public static void growlHTTP() {
        render();
    }

    public static void growlWS() {
        render();
    }

    public static void notifyWithGrowl(String message) {
        Notifier.notify(message);
    }
}

growlHTTPがHTTP経由で、growlWSがWebSocket経由でメッセージをサーバへ送信するページを表示するアクションです。
HTTP経由のメッセージ送信は、notifyWithGrowlへHTTP POSTを送信することで行います。

WebSocket経由


WebSocketクライアントからのメッセージ送受信はこのように行います。

public class Notifications extends Controller {
    public static void index() {
        render();
    }

    public static class WebSocket extends WebSocketController {

        public static void connect() {
            Logger.info("Connected.");

            F.EventStream eventStream = NotificationStream.stream();

            while(inbound.isOpen()) {
                F.Either receivedEvent = await(F.Promise.waitEither(
                        inbound.nextEvent(),
                        // 以下のようにするとNotificationが無限に返ってきてアプリが応答しなくなります。
                        // F.EventStream eventStream = NotificationStream.stream();
                        eventStream.nextEvent()
                ));

                // WebSocketから受け取ったメッセージをGrowlや他のWebSocketコネクションにブロードキャストする。
                for (String message : TextFrame.match(receivedEvent._1)) {
                    Notifier.notify(message);
                    Logger.info("Message received: %s", message);
                }

                // 他のWebSocket接続から受け取ったメッセージを、現在のWebSocket接続先に送る。
                for (Notification notification : ClassOf(Notification.class).match(receivedEvent._2)) {
                    Logger.info("Matched to Notification class.");
                    outbound.send(notification.message);
                }

                for (Http.WebSocketClose close : SocketClosed.match(receivedEvent._1)) {
                    disconnect();
                }
            }
        }
    }
}

この例ではEventStreamは、PlayアプリからWebSocketクライアントと間接的にメッセージ交換をするためのパイプの役割を持ちます。
Playアプリと全WebSocketクライアントが通信するための「バス」もしくは「ハブ」とも言えます。

通信とデータの流れとしてはこうです。

Application.notifyWithGrowl()のリクエスト
または
Notifications.WebSocket.connect()でWebSocketのメッセージ受信
↓
Notifier.notify(message)
↓
ArchivedEventStream#publish(new Notification(message))
↓
Notificationが「接続中の全WebSocketクライアントの」
Notifications.WebSocket.connect()におけるeventStream.nextEvent()から返ってくる。

あるHTTP/WebSocketクライアントのリクエストやジョブから、別のコネクションを貼っているWebSocketクライアントにメッセージを送る場合、EventStreamを経由する以外に方法はありません。

まとめ

  • HTTPやWebSocket経由でメッセージを受信しました
  • 受信したメッセージをサーバへGrowl通知しつつ、他のWebSocketクライアントへ送信しました。
基本的には誰得でございますが、
スライド発表中にこういうアプリでリアルタイムにコメントをもらうと面白いかもしれませんw

参考


Growl notifications in Java with script engine and AppleScript | Jayway Team Blog - Sharing Experience

Play frameworkのchatサンプル

0 件のコメント:

コメントを投稿