いくつかsub protocolがあるようなので調べてみる。
WAMP
WAMPの目的と概要
WAMPの目的は Unified Application Routing を提供すること。
UARとは、アプリケーション間の
- イベント(pub-subなどが例)
- 呼び出しのルーティング(原文: routing of calls, RPCなど)
を1つのプロトコルで提供することができる。
クライアント・サーバ間でのRPCモデルでは、RPCは呼び出し元から呼び出し先に直接送られる。
このモデルでは、呼び出し元は以下について知っている必要がある:
- 存在する呼び出し先
- どのようにメッセージを到達させるか
これでは、アプリケーションはすぐに複雑になりメンテナンス不可能になってしまうためよい方法ではない。
pub-subモデルでは、配信者
は抽象的なトピック
に情報を配信する。そして購読
は、興味のあるトピック
を購読することを宣言することによって、配信された情報を受取ることができる。
両者はお互いについて知ることはない。
配信者と購読者は、トピック
と中間に存在する、いわゆるブローカー
によって分離されている。
TODO: ※ pub-subモデルについては省略
WAMPはRPCに対して、疎結合であることの恩恵を受けられるようになっている。
クライアント・サーバモデルとは異なり、WAMPは「呼び出し元」と「呼び出し先」を中間のディーラー(※原文: Dealer)
を導入することによって分離する。
pob-subのブローカー
と同様に、ディーラー
は呼び出し元から呼び出し先へコールをルーティングし、その結果やエラーを返送することが責務となっている。
呼び出し元と呼び出し先は、どちらもお互いに中間地点がどこにあって、どのようにコールが到達するかを知ることはない。こういったことはディーラー
によって隠蔽されている。
WAMPでは、呼び出し先はprocedureを(手続き、操作)一意のURIとして登録する。 呼び出し元がremote procedureをコールしたいとき、呼び出し元はディーラーにコールされるprocedureのURIと引数を伝えるだけでよい。 ディーラーは、自身が持っているbookの中から登録しているprocedureの中からそのprocedureを探し出してコールする。 ディーラーが持っているbookには、存在するprocedureを実装している呼び出し先がどこにいるか、そしてどのように到達できるかについての情報が含まれている。
もしpub-subとRPCを組み合わせたい場合はどのようにするだろうか?ブローカーとディーラーを組み合わせたい場合は、WAMPのルーター
を使うことができる。
ルーターは、コール(呼び出し)とイベント(pub-subのイベント)をどちらもルーティングすることができる。そのため、RPCとpub-sub両方を使うような柔軟で分離されたアーキテクチャをサポートする。
例を挙げる。 あなたは小さな組み込みデバイス、Arduino Yunを持っていて、さらにそれにはセンサ(温度センサなど)とアクチュエータ(ライトやモータなど)が接続されている。 そして、あなたはこのデバイスを以下のシステムと連携させたい:
- ユーザがアクチュエータを操作するフロントエンド
- 継続的にセンサの値を処理するバックエンドコンポーネント
WAMPを使うことによって、あなたはブラウザベースのUI、組み込みデバイス、そしてバックエンドをお互いにリアルタイムに通信させることができる。
デバイスのライトをオンにする操作はブラウザベースのUIによって行うことができる。これは、デバイスの remote procedure をコールすることによって実行される。(上図の1) そして、デバイスによって継続的に生成されるセンサの値は、配信と購読機能(publish & subscribe)を介してバックエンドコンポーネント(あるいは何か他のもの)に送信される。(上図の2)
これで、1つのプロトコルで「すべての」アプリケーションの通信の要件を満たすことができる。
WebSocket
WAMPを設計しているときに、WAMP開発チームは早い段階でWebSocketはWAMPにとって利用的な基盤であると気づいたという。 WAMPは双方向のリアルタイム通信を提供し、Webとブラウザに互換性があるからである。
しかしながら、WebSocketはかなりのローレベルのプロトコルであり、ただのメッセージング機能しか提供していない。 ここがWAMPが導入されるところである。 WAMPはハイレベルなメッセージング・パターン – RPCやPubSub ー をWebSocketに加えることができる。
厳密に言うと、WAMPはWebSocket subprotocolの1つとして公式に登録されている。 これはJSONをメッセージのシリアライゼーションフォーマットとして使っている。
JSONシリアライゼーションを使ったWAMP-over-WebSocketはWAMPにとって好ましい転送方式だが、このプロトコルはMsgPackをシリアライゼーションとして使うこともできる。 また、生のTCPや、他のメッセージベースで双方向性があり、信頼性のある転送方式で用いることもできる。
そのため、WAMPはWebとそれ以外のどこでも使うことができる。
他のプロトコルとの比較
仕様
IETFのサイトのドラフトがある。
draft-oberstet-hybi-tavendo-wamp-02 - The Web Application Messaging Protocol
以下は概要だけのメモ。
ブロック
WAMPメッセージは次のようなブロックで構成されている。
- Identifiers
- URI
* URIは以下のリソースである必要がある
- Topics
- Procedures
- Errors * URIは、Unicode文字列である必要がある。JSONをシリアライゼーションに使う場合は符号化方式はUTF-8とする * 例: “com.myapp.mytopic1” “com.myapp.myprocedure1”
- Serialications
- WAMPはメッセージベースのプロトコルで、メッセージのシリアライゼーションを必要とする。メッセージは8bit単位で送られる。
- メッセージのシリアライゼーションのフォーマットは以下を想定している。WAMPはJSON data typeのnumber(non-integer)、nullは使用しない。
- integer(non-negative)
- string(UTF-8)
- bool
- list
- dict(keyはstring)
- WAMPは以下の2つのメッセージバインディングを定義している
- JSON
- MsgPack
- Transports
- WAMP実装はは以下のtransportタイプを最低1つはサポートしていなければいけない
- message-based
- reliable
- ordered
- bidirectional(full-duplex)
メッセージ
JSON の payload は、ドラフトと合わせて下記の実装を見るとわかりやすい。
_send_wamp
でハイライトさせていくと、いろいろな操作のメッセージの JSON payload の構成がわかる。
https://github.com/crossbario/autobahn-js/blob/master/package/lib/session.js#L1180-L1181
実装
Java
-
JSONペイロード生成部分
JavaScript
Erlang
- クライアント: bwegh/awre: A wamp.ws client written in erlang
- ルータ: bwegh/erwa: A wamp.ws router written in Erlang.
例えば、Backend with WAMP Client --(WAMP call/publish)--> Redis <--(Redis Channel subscribe)-- Erlang WAMP Router Server --(WAMP pusblish/call)--> WAMP Client
という構成にするとしたら、以下の実装が必要になる:
- Backend は、WAMP の payload の仕様どおりの JSON を Redis に publish する
- Erlang WAMP Router Server は、 以下を行う
- WAMP over WebSocketの接続を受け付ける
- eredis で Redis の channel を subscribeする
- 受け取った WAMP payload から、 WAMP の RPC/PubSub 操作を実行する
- 必要に応じて WAMP over websocket のセッションプロセスにメッセージを送信する
- Brokerなど、一部は bwegh/erwaのモジュールを流用できそう。
- 必要に応じて WAMP over websocket のセッションプロセスにメッセージを送信する
所感
Pros
- メッセージ送受信をpub-sub, message_serverへのユーザ情報更新などの通知をrpcを使ってわかりやすく表現できる
- チャットクライアントは、メッセージ受信のためにsubscribeするのみ
- API Backendサーバは、ユーザ情報などの更新を rpcを 使って call する。call は基本的に message_server のみが受け取る。
※ ただ、クライアント側もrpcを使って特定のメッセージが来たときにハンドラをつける、という実装がライブラリ経由で楽にできる。現状だとping/pongとか。
connection.onopen(session, details) {
var add2 = function(args) {
return args[0] + args[1];
};
session.register('com.myapp.add2', add2);
}
Cons
- Java の API Backendでそのままクライアントライブラリを使うことはできない。WAMPメッセージ生成するところだけ流用して、メッセージをRedisにpublishする必要がある
- Erlang でもすでにあるRouterをそのまま使うことはできない。一部流用しながら独自実装が必要