by Yitzchak Schaffer (@YitzOfTheBits)

In an earlier investigation, I conducted a very high-level comparison of WebSockets to AJAX. I discovered that WebSockets, with its inherently persistent connection, makes more sense in principle for a low-latency interactive application, but that there is no built-in semantic for requests and responses in the way that AJAX inherits from HTTP. Enter the ill-named protocol of WAMP.

Web Application Messaging Protocol (WAMP)

Boom. This is pretty much exactly what I had been looking for. WAMP (http://wamp.ws/) is a subprotocol of WebSockets, in that it specifies a communication semantic for messages sent over WebSockets.

WAMP identifies two planes of communication (two “patterns”): informational messages and commands. Messages can be sent (“published”) and listened for (“subscribed to”), which WAMP calls the “PubSub” pattern. Commands can also be issued (“called”), implemented, and responded to. WAMP refers to this as the pattern of “RPC,” Remote Procedure Call.

In this scheme, a middle layer (“Router”) is introduced between sending and receiving parties (“Clients”). Each Client (e.g. a browser or application server) can register itself with the Router as subscribing to a certain message type, or implementing a certain procedure type. The Router is responsible for receiving messages, distributing them to the appropriate Clients, and responding to or acknowledging most messages in a sensible way.

The Router with the Clients are referred to collectively as “Peers.”

Use in a typical web application

So in the case of a real-time collaborative web application, each user’s browser would register itself as a listener to the appropriate message types, and each application server would register itself as a provider of the appropriate procedure calls, and possibly messages as well. So to represent a user joining the chat room called “WampChat” and sending a message, the flow might look like this:

Browser which is joining, contacts router

12345 is a session ID generated by the server

44978 is a request ID generated by the browser

64621 is a publication ID generated by the server

Flow:

Browser: ["HELLO", "com.example.chat", {}] Router: ["WELCOME", 12345, {}] Browser: ["PUBLISH", 44978, {}, "com.example.chat.joinroom", [], {"user":"bilbo", "room":"WampChat"}] Router: ["PUBLISHED", 44978, 64621]

Router broadcasts join event

78945 is the “joinroom” subscriber ID of a browser already connected to the room

Server: ["EVENT", 78945, 64621, {"user":"bilbo", "room":"WampChat"}]

Browser requests that a user be kicked

34236 is the request ID for the post, generated by the browser

Browser: ["CALL", "com.example.chat.kickuser", 34236, {}, [], {"room":"WampChat", "user":"trollza987"}]

Router requests fulfillment of the kick

89731 is the registration ID of a server already connected to handle “kickuser” calls

Router: ["INVOCATION", 34236, 89731, {}, [], {"room":"WampChat", "user":"trollza987"}]

Server performs, and result is sent back to browser

Server: ["YIELD", 34236, {}, ["OK"]] Router: ["RESULT", 34236, {} ["OK"]]

Load balancing the routers

One thing I’m not sure about is how to load-balance Routers. As far as the other Peers are concerned, there is one endpoint for the Router that they need to deal with, so registering listeners and RPC-handlers with a given Router instance means that all Router instances need to be made aware of this. It might be done via a shared Memcached or Redis instance, as in this very ominously stormy diagram: