Faye is a set of tools for dirt-simple publish-subscribe messaging between web clients. It ships with easy-to-use message routing servers for Node.js and Rack applications, and clients that can be used on the server and in the browser.
Faye is an implementation of the Bayeux prototcol (svn.cometd.com/trunk/bayeux/bayeux.html), a publish-subscribe messaging protocol designed primarily to allow client-side JavaScript programs to send messages to each other with low latency over HTTP. It also allows for server-side clients that let your backend applications push data to the client side.
Bayeux works by letting clients publish and subscribe to named data channels. For example, one client may publish a message:
clientA.publish('/foo', {hello: 'world'});
And another client can subscribe to that channel to receive messages that are published to it:
// alerts "world" clientB.subscribe('/foo', function(message) { alert(message.hello); });
Faye’s messaging backend was originally developed in Ruby for a toy project of mine, but was not written to scale across multiple processes as required for deployment under Passenger. It has since been ported to Node.js which is better designed for handling highly concurrent traffic. If you use the Ruby version, I recommend deploying it behind Thin, an event-driver Ruby web server.
The two backends are architecturally identical, using evented messaging throughout and maintaining subscription data in memory. Neither is geared up yet for running multi-process servers, but their event-driven setup should let them handle more concurrent connections that a typical threaded server.
The JavaScript client and Node.js server are in the build
directory. Just copy them onto your machine and require('path/to/faye')
.
The Rack server is distributed as a Ruby gem:
sudo gem install faye
Both backends allow you to specify a ‘mount point’ that the message server accepts requests on. Say you set this to /faye
, the client script will be available from /faye.js
and should connect to /faye
.
You should set up the client as follows:
<script type="text/javascript" src="/faye.js"></script> <script type="text/javascript"> fayeClient = new Faye.Client('/faye'); </script>
Take care only to have one instance of the client per page; since each one opens a long-running request you will hit the two-requests-per-host limit and block all other Ajax calls if you use more than one client.
This client object can be used to publish and subscribe to named channels:
fayeClient.subscribe('/path/to/channel', function(message) { // process received message object }); fayeClient.publish('/some/other/channel', {foo: 'bar'});
You can publish arbitrary JavaScript objects to a channel, and the object will be transmitted as the message
parameter to any subscribers to that channel. Channel names must be formatted as absolute path names as shown. Channels beginning with /meta/
are reserved for use by the messaging protocol and may not be subscribed to.
The client can also be used cross-domain if connecting to a backend that supports callback polling (see below under ‘Transports’). Just pass in the full path to the endpoint including the domain. Faye figures out whether the server is on the same domain and uses an appropriate transport.
fayeClient = new Faye.Client('http://example.com/faye');
The Bayeux spec defines several transport mechanisms for clients to establish low-latency connections with the server, the two required types being long-polling
and callback-polling
.
long-polling
is where the client makes an XMLHttpRequest
to the server, and the server waits until it has new messages for that client before it returns a response. Faye’s client and server backends all support this transport. Since it uses XHR, the server endpoint must be on the same domain as the client page.
callback-polling
involves the client using JSON-P to make the request. The server wraps its JSON responses in a JavaScript function call that the client then executes. This transport does not require the client and server to be on the same domain. Faye’s client supports this transport, as does the Node.js backend. The Rack backend supports it if running under Thin.
Faye also supports an in-process
transport, which is used when a server-side client has direct in-memory access to the server without going over HTTP.
Both the Node.js and Rack backends have identical architectures and are designed to be easily plugged into other web services. For Rack the adapter is explicitly designed as middleware, while for Node.js the adapter is a simple object you can manually offload requests to.
The backends provide a service for routing messages between clients; no server-side programming is needed to control them, just start them up and they’ll sit there merrily chewing through requests. They are both currently single-process since they hold all channel subscriptions in memory. This means, for example, that the Rack backend will not work under Passenger since that spawns multiple Ruby processes to serve your site.
Faye uses async messaging internally so nothing blocks while waiting for new messages. If running under a Ruby web server (except Thin) the server will block while waiting for a response from Faye. Thin supports async responses and is a better choice for long-running concurrent connections.
Both backends support the following initialization options:
-
mount
- the path at which the Faye service is accessible. e.g. if set to/faye
, the Faye endpoint will be athttp://yoursite.com/faye
and the client script athttp://yoursite.com/faye.js
. -
timeout
- the maximum time (seconds) to hold a long-running request open before returning a response. This must be smaller than the timeout on your frontend webserver to make sure Faye sends a response before the server kills the connection.
Usage examples and a demo app are in the examples
directory.
Here’s a very simple Node web server that offloads requests to the messaging service to Faye and deals with all other requests itself. The Faye object returns true
or false
to indicate whether it handled the request. You’ll need faye.js
and faye-client-min.js
in the same directory.
var http = require('http') faye = require('./faye'); var server = new faye.NodeAdapter({mount: '/faye', timeout: 45}); http.createServer(function(request, response) { if (server.call(request, response)) return; response.sendHeader(200, {'Content-Type': 'text/plain'}); response.write('Hello, non-Faye request!'); response.close(); }).listen(9292);
Faye’s JavaScript client can be used server-side under Node. There are two ways you can set a client up: either connect to as remote server over HTTP or connect directly to the NodeAdapter
. Either way, the API for the client is exactly as it is in the browser.
// Remote client client = new Faye.Client('http://example.com/faye'); // Local client server = new Faye.NodeAdapter(options); client = server.getClient();
Note getClient()
returns the same client object every time you call it, so any subscriptions you make with it are retained.
Faye can be installed as middleware in front of any Rack application. The Rack backend uses EventMachine for asynchronous message distribution and timeouts. It can run under any web server, though Thin is best placed for handling long-running concurrent connections and supports async server responses. Under other servers the request thread will block while waiting for a response from Faye.
Here’s a config.ru
for running it with Sinatra:
require 'rubygems' require 'faye' require 'sinatra' require 'path/to/sinatra/app' use Faye::RackAdapter, :mount => '/faye', :timeout => 25 run Sinatra::Application
This functions much the same as the Node.js example; Faye catches messaging requests and deals with them, letting all other requests fall down the stack of Rack middlewares.
Again mirroring the Node tools, Faye has a client-side Ruby client that cna be used under EventMachine. Setup is identical:
# Remote client client = Faye::Client.new('http://example.com/faye') # Local client server = Faye::RackAdapter.new(options) client = server.get_client
Both clients must be run inside EventMachine, and can publish and subscribe just like the JavaScript client:
EM.run { client.subscribe('/from/*') do |message| # do something with message hash end client.publish('/from/jcoglan', 'hello' => 'world') }
If you want to hack on Faye, you’ll need Node.js, Ruby and the following gems installed:
sudo gem install eventmachine em-http-request rack thin json jake
DO NOT edit any JavaScript files outside the javascript
and test
directories. They are generated using Jake, which you should run after editing the JavaScript source.
jake -f
Ordinarily I would keep generated files out of the repo but I’m keeping some in here as it’s currently the only place to download them.
If you want to submit patches, they’re far more likely to make it in if you update both the Ruby and JavaScript versions with equivalent changes where appropriate.
-
Investigate WebSockets as a possible message transport
-
Provide support for user-defined
/service/*
channels -
Allow server to scale to multiple nodes
(The MIT License)
Copyright © 2009-2010 James Coglan
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.