連載
» 2010年10月20日 00時00分 公開

WebSocketの現状と技術的課題WebSocketで目指せ! リアルタイムWeb(2)(2/2 ページ)

[井上真,New Bamboo]
前のページへ 1|2       

Pusher誕生の背景

 さて、WebSocketを使って、いくつかサンプルを作っていたのですが、やはり実際に使えるアプリを作ってみたくなるものです。もちろんnode.jsなどを使って、いちからリアルタイムなアプリを作るのも良いのですが、「既存のアプリにリアルタイムな機能を簡単に追加できれば良いよね」と、社内でよく話していました。また、Google App EngineやHerokuといったPaaS(Platform as a Service)上でも使えたら良いよね、という意見がありました。

 そのとき、弊社のディレクターである@maxthelionが「クライアント→サーバ間はHTTP Post(Ajax)で、サーバ→クライアント間はWebSocketで」というアーキテクチャを提唱し、@mlaughranが一気にプロトタイプを作り上げました。これがWebSocketをクラウドで利用しやすくする私たちのサービス「Pusher」誕生の背景です。

Pusherはnode.jsでできているの?

 ここまで読まれた読者の皆さんは「いよいよnode.jsの実践投入か」と思われたかもしれませんが、Pusherは「em-websocket」というRubyのEventMachineを利用したライブラリを使用しています。私たちがRubyを採用した理由ですが、New Bamboo自体がRubyに特化したシステム開発会社なので、Rubyのほうが開発スピードが早いというのが挙げられます。また、node.jsは当時はまだ若いフレームワークだったのでデータベースやメッセージングシステムなどの、ほかのコンポーネントを利用するライブラリが出そろっていなかったり、まだ発展途上だったりしたというのも理由です。

 ちなみにPusherという言葉は「麻薬の売人」のスラングでもあります。「サーバ→クライアント間をPushする」という意味とともに、「麻薬のように中毒性のあるほど魅力的なアプリを作る橋渡し人」と言う意味も掛け合わせました。

Pusherのアーキテクチャ

 アーキテクチャを図にすると以下のようになります。

Pusherのアーキテクチャ Pusherのアーキテクチャ

 「クライアント→Webサーバ→Pusher WebSocketサーバ」の上りは、AjaxやRESTといった従来のHTTPリクエストを用いて行います。そして「Pusher WebSocketサーバ→クライアント」の下り側のみWebSocketのコネクションを利用してデータをプッシュするモデルです。

 こうすることでWebサーバ側は旧来のステートレスな枠組みの中で簡単に「プッシュ機能」を取り付けることができるようになります。

Pusherの使用例

 使い方に関してはPusherホームページの「クイックスタート」のページにあるのですが、それだと面白くないので、ここでは実際のオープンソースアプリを元に解説していきましょう。サンプルは私の会社の元同僚であるPaul Jensenが作った「Retrospectiveapp」です。全コードはGithubにあります。

retrospectiveappのスクリーンキャスト retrospectiveappのスクリーンキャスト

 「レトロスペクティブ」というのはアジャイルソフト開発に良く使われる手法の1つなのですが、各イテレーション(反復期間)ごとに開かれる反省会のことです。各メンバーは「良かったこと」「悪かったこと」などをポストイットに書き出し、グループごとに分けたりしながら議論することで、次回のイテレーションに向けた改善点を探し出すという手法です。

 では実際にコードの一部を見てみましょう。

まずはクライアントサイドのコードです。

pusher = new Pusher(Pusher.key);
pusher.subscribe(Pusher.channel);
pusher.bind('note-create', function(note) {
  generateNote(note);
});
pusher.bind('note-destroy', function(data) {
  $("#note_"+data.id).remove();
});
pusher.bind('note-update', function(note) {
  updateNote(note);
});
pusher.bind('note-softupdate', function(note) {
  updateNote(note);
});

 1行目の「pusher = new Pusher(Pusher.key);」でWebSocketを包んだPusherオブジェクトを作成します。1番目の引数の“Pusher.key”というところにはPusherにサインアップした時に与えられるキーを割り振ります。

 そして2行目の「pusher.subscribe(Pusher.channel)」というところにチャネル名を割り振ります。「チャネル名」という聞き慣れない言葉が出ましたが、これはPusherにおいてPubSubモデルを実現するための仕組みです。「ブロードキャスト」というのはサーバに接続しているブラウザのすべてにメッセージを送ることですが、実際には特定のユーザーにのみ、特定の情報を送るのが普通です。そこでまずユーザーがチャネルAに自身を登録(このことをSubscribeと呼びます)し、データの送り手は(この場合Webサーバ)はチャネルAにのみデータを供給します(このことをPublishと呼びます)。チャネル名に何を当てるかは人それぞれです。各ブログエントリーごとに「blog_posts_1_comments」のようなコメントチャネルをつけても良いですし、ユーザーごとにチャネル名を割り当ててみても面白いかもしれません。

 3行目以降「pushere.bind('イベント名', function(note) {});」のようなコードが続いています。WebSocketにはもともと「onmessage」というイベントハンドラがあるのですが、これにはWebSocketから送られてくるデータすべてに反応します。そうするとデータの種類によってロジックを分けるためのif分が多いコードになってしまいがちです。

 そこでPusherではイベントごとにバインドするモデルを採用することでコードを細かくまとめやすくなっています。上の例では各ユーザーが自分でコードを定義していますが、Pusher自身も最近のバージョン(1.6)の「プレゼンス機能」でイベントを多用しています。どういうことかというと、自分がSubscribeしているチャンネルに、ほかのメンバーが接続すると「pusher:member_added」、切断された時には 「pusher:member_removed」という情報を送るようにしています。

 次にサーバサイドのコードの抜粋です。サーバサイドの例にはSinatraというRuby製の軽量Webフレームワークが使われています。

PUSHER_CHANNEL = 'retrospectiveapp-' + Sinatra::Application.environment.to_s
put '/notes/:id/softupdate.json' do
  content_type 'text/json', :charset => 'utf-8'
  Pusher[PUSHER_CHANNEL].trigger_async('note-softupdate', params[:note].to_json, params[:socket_id])
  params[:note].to_json
end

 ここでもチャネルとイベントごとにデータをPushしているのが分かるでしょうか? この例ではPusherというRuby用のライブラリを使っていますが、そのほかの言語のライブラリもそろっています。また、特定の言語のライブラリが存在しない場合でも、普通のHTTPクライアントを使ってPush可能です。

 実際、Pusherにアカウントを作成すると、以下のようなコマンドが表示され、これをコマンドプロンプト上でコピー&ペーストすることでPushすることも可能です。

curl -d "hello world" \
"http://api.pusherapp.com/apps/app_id/channels/test_channel/events?"\
"auth_version=1.0&"\
"auth_key=94132f5d219216&"\
"body_md5=01eeed093cb22bb8f5acdc3&"\
"auth_timestamp=1287600207&"\
"name=my_event&"\
"auth_signature=4205808619016f070384ef5"

 Pusherではこのほかにも、みなさんの開発に役立つデバッギング用コンソールや、どれぐらいのメッセージ数や接続数があったかをグラフで見るこなどができます。ぜひ一度登録してご自分で色々試してみてください。

XMPP、AMQP、STOMPとは何が違うの?

 「PubSub」とか「プレゼンス」などといった言葉が出てきた時点で、「それってまさにXMPP(Extensible Messaging and Presence Protocol)じゃない?」と思われた読者も多いのではないかと思います。

 XMPPは10年以上の歴史を持つプロトコルである上に、Extensibleとある分、機能も豊富です。

 XMPPをブラウザから使うためのJavaScriptライブラリとしては、BOSH(Bidirectional-streams Over Synchronous HTTP)というCometと非常に似たプロトコルを使用したStropheというのが有名なのですが、今年の始めに開発者向けメーリングリストでWebSocketサポート予定を聞いたところ、「もっとドラフトが固まってから」とのことでした。

 もしXMPPをブラウザ上から使うことに興味がある方は、Stropheか、エンタープライズな環境であればKaazingを使うことをお勧めします。Kazzing社は、ChromiumがWebSocketを実装する前から、FlashSocketやLong Pollingを使用したエミュレーション環境を提供している上に、XMPP、STOMP、AMQP用のJavaScriptライブラリも提供しているはずです。ちなみにKaazing社はWebSocketの普及にも熱心なようで、私たちも昨年の12月にKaazing社の主催するトレーニングに参加し、WebSocketに関する知識をそこでたくさん得ました。

 STOMP(Streaming Text Oriented Message Protocol)についてはサーバサイドはActiveMQが、クライアントサイドJavaScriptライブラリとしてはstomp-websocketというのがWebSocketをサポートしています。

 AMQP(Advanced Message Queuing Protocol)に関してですが、Pusherの内部でWebSocketサーバをスケールアウトさせるための手段の1つとしてRabbitMQを使用しています。

 これらスタンダードメッセージングプロトコルとWebSocketの違いですが、私たちのPusherは、既存のWebサーバとのインテグレーションのしやすさを一番の重点に置いていることです。またPusherはクラウドサービスとして提供しているため、各自が自前でメッセージングサーバを立ち上げなくても良いというのも長所の1つです。

 さて、今回はここまでです。WebSocketの変わり続ける仕様の問題、ブラウザ実装の状況に加えて、私たちが立ち上げた「Pusher」についてご紹介しました。次回はWebSocketの実例を多めに取り上げつつ、WebSocketの今後の可能性についての私の予想を書いてみたいと思います。


前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。