I’m doing my traditional birthday software announcement a little early this year, mostly because I really want to get this out and partly because I’m doing a lot of little bits of work on old projects at the moment and this is the only fancy new thing I’ve got to show.
Spurred on by the sheer volume of awesome emanating from Music Hack Day over the weekend – a few Songkickers went over, I caught the demos via the live stream – I decided to revisit a project I worked on just before the London MHD about six months ago. My hack was this little web service that exposed your iTunes library over HTTP and let you broadcast what you were listening to to other browsers via Comet. The Comet part of it was handled by an early version of Faye, a Ruby server and JavaScript client I cooked up to provide client-to-client messaging.
The Ruby version still exists, and just gained support for Thin’s async response feature, making it totally non-blocking when used behind Thin. The more exciting news is that I’ve also ported the backend to Node.js, a JavaScript environment optimised for non-blocking asynchronous I/O. This makes it much better at serving highly concurrent Comet requests since it uses an event loop rather than blocking threads to respond to each request. It’s a straight port of the Ruby version, which is entirely event-driven except for the web server interface, since Rack expects a synchronous return to respond to requests.
Anyway, onto the code. The backends are trivial to set up, and both currently reside in a single process and keep all channel data in memory. This means you can’t use it behind Passenger (which spawns multiple Ruby processes to serve an app) but I’m hoping some bright spark can help with scaling it. Consider it a fun and reasonably nippy toy right now, I guess. To set up a backend:
// JavaScript
var faye = require('./faye');
var comet = new faye.NodeAdapter({mount: '/comet'});
# Ruby
require 'faye'
comet = Faye::RackAdapter.new(:mount => '/comet')
More complete usage examples are available in the Git repo. Both these backends provide an endpoint at http://0.0.0.0/comet that speaks the Bayeux protocol, and clients can use this to route messages between each other.
Setting up the client is a snap:
<script type="text/javascript" src="/comet.js"></script>
<script type="text/javascript">
CometClient = new Faye.Client('/comet');
CometClient.connect();
</script>
Each client (i.e. a JavaScript program running in a browser) can subscribe to named channels and publish arbitrary JavaScript objects to channels. Each message is distributed by the server to all the clients subscribed to that channel, and clients use a callback to handle messages from the channel.
// I can publish a message ...
CometClient.publish('/my/channel', {msg: 'Hello world!'});
// .. and you can pick it up
CometClient.subscribe('/my/channel', function(message) {
alert(message.msg);
});
Using this it’s absolutely trivial to make simple client-to-client messaging apps – the repo contains the obligatory chat demo, which I’ve modelled on Twitter to illustrate different uses of subscriptions. You don’t need any server-side code for this aside from starting the Faye backend up to handle message routing. The next step would be to provide a client interface on the server side so your backend code can send data out to clients, but we’ll save that for a future release. Contributions in the form of scaling advice and server-side client suggestions would be very much appreciated, in the meantime go have a play with the code.
That is exactly the piece of code I was looking for! Thank you!
Ajaxian » Faye: Bayeaux protocol Comet server for Node.js
Hi, can you use Faye to connect to itself from within Node.js and subscribe some channels? I’d like to monitor some channels to keep a backlog of messages in memory, and serve the backlog to clients through another URL.
Kenneth, I’m working on a set of server-side clients at the moment. For Node, I’m taking the browser client and adding new transports for Node’s HTTP API and for in-process client-server communication. The Bayeux protocol allows in-process clients special access to server state so it’s a little work to implement this. Once I’m happy with it I need to backport the client to Ruby before getting a release out.
You can track progress on this branch: http://github.com/jcoglan/faye/tree/clients
Hello James, have looked through commit history and it seems to me that you did not yet implemented a way to invoke publish() on server side yet, am I right?
I have implemented server-side clients that can publish and subscribe, they’ve just not been released yet. Hopefully I’ll get them released this week, in the meantime there’s information in the README on GitHub, for example under Node you’d do:
And use the client just like you would in a browser.
Works flawlessly so far, great job, thank you!
Nice piece of work! However it’d be nice if there were some support for error handling. ‘Looks like your XHR transport code quietly ignores a variety of error cases.
I’m looking specifically at the case where the server gets restarted during a long-poll request. In this case, there’s no response and the Faye clients simply stop polling. At a minimum, there should be a way to configure the behavior in this case (e.g. “retry every 10 seconds”)
Also, I’d prefer to have a way to adjust the polling frequency of the Faye.XHR readystate check. (10ms strikes me as a bit aggressive.)
… oh, and also, you might consider doing this:
… instead of using setInterval. It’s simpler, and avoids potential issues with multiple setInterval calls getting queued up if CPU performance lags for whatever reason.
make that “return setTimeout(…)”
Robert, error handling was on my to-do list and you’ll be happy to know it just got bumped. I just made a commit that reconnects a client with the server, including maintaining its channel subscriptions, if the server goes away for whatever reason. It does this by setting a time limit within which it expects to get a reply to connect(), since the various connect transports don’t all allow for a robust means of error detection on the request.
This is a first attempt and certainly the timeout needs to be able to be tuned, but let me know what you think.
Great work. Its perfect for a project that I was trasnlating from c# to java, but this is much better.
Have a question though. In, for example, cometd, on the client (in a browser), the comet client connects and waits until the connection is confirmed before a subscription can be set. Is there a way of knowing when the conneciton is succesfully with faye?
cheers and great work.
Camilo: You can, but you don’t need to worry in this case since Faye makes sure it has a valid connection before sending subscriptions. If you need to make sure a connection is present before running some code, you can use the connect() method with a callback, which is what Faye does internally:
connect() simply makes sure there is a connection before running the callback function.
Quora
Are there any good tutorials for building a Comet application using Ruby on Rails? - Quora
Comet or Long Loop message pattern – put an end to polling! | Allan's Ramblings
» Comet or Long Loop message pattern – put an end to polling! Allan's Ramblings