Push is complex. I tried to figure it out with ActiveMQ, Orbited, and friends awhile back, but it just seemed too complicated. It made me feel like I was not very smart—obviously, some people understand this thing, but I don’t. I am a bit too prideful, and do not like feeling unintelligent.
But I need messaging.
I don’t care about queuing or persistence. For web applications (what I work on) messages should be delivered as quickly as possible. Any “queues” can be in-memory, and any message should not remain in a queue for more than a minute—if the client was disconnected for any duration, it would likely be better off re-downloading that receiving incremental updates.
So, whatever these systems do, they seem too complicated (I could be wrong—as I said, I have trouble understanding them).
My requirements:
- Normal Authentication. Clients should go through the “normal” web app to subscribe to events.
- Easy. It should be very simple and easy to understand.
- Quick. I should be able to add Comet to an app in ten to fifteen minutes, maximum.
I found out that it is not only possible to write messaging servers on one’s own—it is relatively easy. So I wrote one.
I’ll stop the backstory here, though there is plenty more—tell me if you want any of the boring details about Dolores and the rest.
Roots
Roots—codenamed Dobby after the Harry Potter character—is a messaging server that has a built-in long-polling server (among several other things).
Roots is made up of many small message-passing pieces. They plug together, just like Lego pieces. Each piece is simple, and the connection between the pieces are even simpler: a single function, called “update.”
If you are interested, I’ll go over that more thoroughly in “Implementation.”
How Roots is Used
Above All, Roots is Easy.
It takes me about fifteen minutes to use Roots to add push to a SproutCore application (starting with a fresh clone of Roots—which, by the way, does not need any configuration for testing). It should only take me five minutes, but I am often a bit slow of a coder.
There is a brief semi-tutorial on GitHub. The following is based on that tutorial, but has some extra background information.
So, a bird’s-eye view: unless you implement the server-side in Roots itself, you will have three components in any application: back-end (Rails, Django, etc.), front-end (SproutCore), and Roots.
- Client—such as Pomona, the SproutCore framework for Roots) connects to a Roots server.
- Roots sends the client (Pomona) an id.
-
The client calls predefined URL to connect to events.
- The URLs are part of the back-end (Django, etc.)
- The ID is sent with these requests.
- Clients (Pomona) usually will keep track of what server-side events you are listening to and can reconnect automatically if the Dobby connection is interrupted—you just provide the client with the URLs and the events to listen to.
- Back-end tells Roots to connect client to events—and can perform any authorization it wishes.
- Back-end sends updates to Roots.
- Client receives notifications—everyone’s Happy!!!
What You Do (for SproutCore)
You don’t have to do much:
- Add Pomona to your SproutCore project.
- Add a Roots connector to your server-side project:
- Cornelius for Python
- Others as I add them (I plan Ruby and PHP)
- Implement your own (the Python version of the better protocol—Dudley—is 45 lines counting whitespace and comments).
- “Update” Roots (call update(path, message) from your back-end—Django has handy model post-save signals you can use).
- Add some URLs to your back-end that connect clients.
- URLs accept client ID in them (/my/connect/AN_ID_HERE or /my/connect.src?id=AN_ID_HERE)
- URLs should accept POST of a simple JSON array of path/event names.
- For each path/event/whatever you want to call it, call connect(id, path) on your Roots connector.
- Initialize Pomona and connect to Roots in your SproutCore Data Source.
- Accept incoming data!
A more detailed tutorial is here on GitHub with the rest of Dobby (Roots).
Open Question: Would anyone like a tutorial on setting up a Django back-end for the SproutCore Todos tutorial, with an extra step of adding Comet at the end? Done!
Implementation
Roots/Dobby is currently written in Python. I am working on a rewrite in node.js, because node.js is super cool—and, secondarily, it means that some of the same code used in the server (for instance, the message dispatch code) could be used on the client.
Roots has many small parts that hook together in a very simple way. At Roots’s core is Thestral, a simple interface that requires implementors to do two things: have an id and implement a single method: update(sender, path, message)—where sender is an originator, path is an event name, and message is some data to send.
Here are some of the components:
- Dolores: This is the main controller. It assigns IDs to all Thestral instances. It itself is a Thestral instance, and sends its updates to “delegates”.
- Pig: An “Owl,” or, really, a dispatcher; it knows who receives what message. It is often one of Dolores’s delegates. Specially-formed messages, tell it to connect certain Thestral ids with certain events—for instance, the event “::connect” with message “12345->my/data” would connect Thestral “12345″ to “my/data”.
- Imperio. A somewhat pathetic text-based protocol, with accompanying receiver server (a bit like Dudley, which should be used instead). In addition to reading from a server, Imperio supports writing to anything with a write() method, and as such, is used for logging as a delegate of Dolores.
- Dudley. Dudley is not a delegate of Dolores. Rather, Dolores can be its “delegate” (or, in this case, “receiver”). Dudley is an HTTP server, and receives messages over HTTP and funnels them to a receivers (Thestral implementation), and Dolores is its default receiver.
- Firenze. Firenze, like Dudley, is an HTTP server; but instead of being a receiving server, it is a long-polling push server. It is not usually Dolores’s delegate—you don’t want every client to get every message. Instead, Firenze merely registers itself with Dolores to get an ID, and lets others register it (for instance, a client could send Dudley a message, which, through Dolores, would be received by Pig to make the connection).
Above all, it is simple. The longest of these pieces is Firenze, at 280 lines of code (50 of which are intro comments I used to explain it to myself and spec it while planning).
That’s All!
I hope you find this helpful. I am excited about porting Roots to node.js; progress is somewhat slow, as I am quite busy (I am a student, and finals for all four classes tomorrow!)
What I am most looking forward to is how Pig (the dispatcher) could use the same code in both the client (Pomona) and the server—the routing is quite similar; in one case, you want to route to handlers/callbacks, in the other, to update functions on Thestral instances—pretty similar; even identical.
I am also looking forward to experimenting with two-way push, where all data is transferred through push—completely eradicating the expectation of a response to a request, and providing only a single receive/send code path on both client and server.
Anyway, I think this is all super cool. :)
Thanks to Endash for the nice title. :)
Roots (called Dobby in development) is available on GitHub.
P.S. I like the name Dobby, but Roots is better P.R.