Software Alchemist

I am constantly seeking answers and ways of transforming desires into reality by coding

Interacting With ZeroMQ From the Browser

| Comments

Interacting with ZeroMQ from the browser is the talk me and my co-worker Jeff Lindsay gave at ZeroMQ conference in Portland, OR. We've missed our flight the night before and I started writing this post while we had been sitting at the gate, waiting for unclaimed seats on the next available flight.

Fixing the world

"World is broken" is the reason ZeroMQ exists, it is there because networking has been unnecessarily hard and had to be fixed. ZeroMQ's philosophy is about modularity and reusability, it promotes creation and use of sockets, networking primitives, that do just one thing and do it well, to compose more complex communication patterns that are simple to think about and communicate.

ZeroMQ in the browser

Design and philosophy of ZeroMQ alone is incredibly useful and opens up mind to new ways of thinking about and solving networking problems. From this point of view, ZeroMQ's actual C library (libzmq) is just an implementation detail. Imagine taking same networking primitives ZeroMQ introduces and solving web related problems with them. NullMQ is taking concepts ZeroMQ introduced and applying them in a different environment. This new environment has a different set of constraints, yet similar requirements of solving communication by reusing basic primitives.

NullMQ gives you the same six socket types to be used in the browser. Browser environment is different from private networks. Additional constraints like authentication, authorization, limited number of connections and speed are added. NullMQ operates over WebSockets and has its own communication protocol based on STOMP. NullMQ to STOMP is like WebDav to HTTP. It is therefore server implementation agnostic. NullMQ context, once instantiated, lets you create same socket types as regular ZeroMQ context. It won't open new connection per each socket however and will instead handle multiplexing of connections,so you get multiple virtual connections, all using one real connection underneath. Having ZeroMQ semantics available in the browser is powerful, because it lets you solve a networking problem by designing appropriate communication pattern from scratch, without being constrained by various browser or server networking specifics, and re-use it in both environments.

NullMQ in action

For our NullMQ demo I build chat and presence servers and clients. For both problems, I used clone pattern from ZeroMQ guide. Server in clone pattern consists of three different sockets, PUB - which is used to publish state updates to all subscribed clients, PULL - which each individual client pushes its state changes to, and which server ultimately ends up publishing to all clients and, finally, ROUTER - which is used to answer client requests to get server's current absolute state. Client also has three sockets, which are almost exact opposites of server's socket set. When client first starts, it uses a SUB socket to subscribe to all state changes that a server will publish, immediately after that, it creates a REQ socket to get server's current absolute state which it will then remember and apply published updates to to stay in sync, and, finally it uses a PUSH socket to send it's own state changes back to the server.

This pattern solves both a chat and a presence use case. In case of a chat, a clients connects, subscribes to new messages and requests all messages previously published to the server. Whenever user sends a message, it pushes them back to the server, server publishes this message to all clients and it ends up on user's screen. In case of a presence server, client connects, subscribes to peers' state updates from the server, requests a list of all peers, which is a list of names and online statuses and starts pushing periodic heartbeat. In my case, it specifies heartbeat timeout in every heartbeat message and server constantly looks at a list of registered clients, checks when the last heartbeat for each client was received and compares it to a timeout that client send out. If more time has passed since the last heartbeat than was specified as timeout, it decides the client is offline and publishes that to others.

Client and server for both use cases had been implemented in Ruby, using ZeroMQ bindings for Ruby. And this is where NullMQ comes into the picture. I created two more clients for my presence and chat servers, this time in javascript in the browser, using NullMQ. It was surprisingly straight forward and code was almost identical in JavaScript and Ruby.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
require "ffi-rzmq"
require "clone"
require "json"

@peers = {}
@name = ARGV[0]
@text = ARGV[1]

client = Clone::Client.new(ZMQ::Context.new(1), {
  :subscribe => "tcp://localhost:10001",
  :request   => "tcp://localhost:10002",
  :push      => "tcp://localhost:10003"
})

client.on_response do |payload|
  begin
    peers = JSON.parse(payload)
    peers.each do |name, peer|
      @peers[name] = peer
    end
  rescue JSON::ParserError
  end
end

client.on_publish do |payload|
  begin
    peer = JSON.parse(payload)
    @peers[peer['name']] ||= {}
    @peers[peer['name']].merge!(peer)
  rescue JSON::ParserError
  end
end

begin
  $stdout << "connecting...\r\n"
  client.connect
  loop do
    client.push(JSON.generate({
      "name" => @name,
      "text" => @text,
      "online" => true,
      "timeout" => 2
    }))
    sleep(1)
  end
rescue Interrupt
  $stdout << "disconnecting...\r\n"
  client.disconnect
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
this.presenceClient = new Client({
    subscribe: "tcp://localhost:10001"
  , request:   "tcp://localhost:10002"
  , push:      "tcp://localhost:10003"
});

this.presenceClient.onResponse = function(payload) {
  Object.merge(this.peers, JSON.parse(payload));
  $updateView();
}.bind(this);

this.presenceClient.onPublish = function(payload) {
  var peer = JSON.parse(payload);
  this.peers[peer['name']] = this.peers[peer['name']] || {}
  Object.merge(this.peers[peer['name']], peer);
  $updateView();
}.bind(this);

var interval;

this.presenceClient.onConnect = function() {
  interval = setInterval(function() {
    this.presenceClient.push(JSON.stringify({
        name: this.name
      , online: true
      , text: this.text
      , timeout: 2
    }));
  }.bind(this), 1000);
}.bind(this);

this.presenceClient.onDisconnect = function() {
  clearInterval(interval);
  this.peers = {};
}.bind(this);

To connect NullMQ clients to ZeroMQ servers, I wrote a WebSocket bridge implementation in Python. It uses ws4py and stomp4py for NullMQ frame processing. When NullMQ JavaScript clients call connect on a socket and pass endpoint URI, the WebSocket Python bridge actually creates a real TCP connection to ZeroMQ Ruby server. Data from JavaScript to bridge is sent over a single WebSocket connection per NullMQ context instance (I had two contexts total in this case - one for chat and one for presence clients), which multiplexes messages from different sockets (three sockets per each client). This data is then processed and outgoing messages are forwarded through real TCP links from bridge to presence and chat servers. Messages from server to bridge are multiplexed back to JavaScript sockets over WebSocket connection.

The joy of realtime

When I finished writing the bridge and started the whole thing I was immediately blown away. Being able to see other clients coming online and getting messages is pure joy, you can't stop smiling. The whole thing took about 3 days of work, which is amazing considering that I used three various languages, one of them for the first time (Python), two libraries I have almost no experience with, and all clients had almost exactly same code (Javascript and Ruby). I could potentially write clients in any language I wanted (ObjectiveC for iPhone app, Java for Android and etc.) and it really feels like there is no limit to what can be built on top of this. For this demo I haven't implemented advanced clone pattern features like numbered updates and automatic synchronization, but it is really not hard to do and I might just throw it in there.

Conclusion

While being a great networking library, ZeroMQ is more than that, it is a networking philosophy and a great source of recipes for common problems. NullMQ proves that this philosophy and knowledge is not limited to traditional, private networking, but also weird, limited control places like web browsers. So take it for a spin and let me know how it goes.

P.S. all sources for our presence and chat demo are available on github, just follow the docs in each directory to be able to run it locally.

Comments