2011年1月31日月曜日

Web Socketなど

とある事情があって、g3にHTML5のWeb SocketやServer-Sent Event機能を追加してみました。
Web SocketはJettyの機能を利用しています。

今回使ったWeb SocketとServer-Sent Eventを使ったg3アプリケーションは以下のものです。

class Html5Service extends G3Application {
  port("/") html(<p>OK</p>)

  port("/sse") agent {
    case _ => {
      EventStream(System.currentTimeMillis.toString, 1000)
    }
  }

  port("/ws/chat") agent {
    case msg: Post => {
      new Post("/chat", msg.content)
    }
  } invoke("wschat")

  websocket('wschat, "/chat")
  websocket('wstimer, "/timer")

  timer("") agent {
    case msg: Post => {
      Post("/timer", "Time: " + System.currentTimeMillis)(
    }
  } invoke("wstimer")
}

動作概念図はこんな感じ。


「/」(e.g. http://example.com/demo/)にアクセスが来ると「OK」が表示されます。これは動作確認のため。

「/sse」(e.g. http://example.com/demo/sse)にアクセスが来ると、以下のようなServer-Sent Eventを返します。MIMEタイプはtext/event-streamです。

retry: 1000

data: 1292615603413

実現方式は簡単で、新たにtext/event-streamなメッセージEventStreamを追加しました。Server-Sent Eventの仕様はシンプルなので、サーバー側の仕組みもいたってシンプルです。
ただ、残念なことに現在の所、ブラウザが想定しているフォーマットと少しずれているのか、Safariではうまく動きませんでした。仕組みは簡単なので、原因が分かれば修正も簡単にできるでしょう。

「/ws/chat」は、チャットの入力となるHttpのURIです。g3のWebSocketチャネル「wschat」を経由して、WebSocketのポート「/ws/chat」にデータを出力しています。

WebSocketのポートとして、「/ws/chat」と「/ws/timer」が公開されています。「/ws/chat」は前述のように、HTMLのURIの「/ws/chat」からループバックして接続されています。このループバックによってチャット機能を実現しています。

「/ws/timer」は、タイマーからg3のWebSocketチャネル「wstimer」を経由して、タイマーからの入力を受取り、WebSocketに送信しています。

g3は、RESTのセマンティクスを軸にコンポーネントを疎結合して、非同期メッセージング、イベント駆動で動作させるフレームワークですが、WebSocketやServer-Sent Eventもシームレスに統合できることが確認できました。
上記のプログラムは、チャネル間を配線しただけの簡単なものですが、アプリケーションロジックをチャネルのエージェントとして配備することでより複雑な処理ができるようになります。
たとえば、一定時間ごとにEvernoteにアクセスして、取得した結果をWeb SocketやServer-Sent Eventでプッシュ配信する、というようなアプリケーションを簡単に作れるでしょう。