Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Simple pub/sub messaging for the web

This branch is 0 commits ahead and 432 commits behind master

Fetching latest commit…

Cannot retrieve the latest commit at this time

README.rdoc

Faye

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.

Introduction

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-driven 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 than a typical threaded server.

Installation

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

Using the client

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', {timeout: 120});
</script>

The client (and the Node.js and Rack server-side clients) accepts the following options in its constructor:

  • timeout - the maximum time (seconds) to wait for a connection response from the server before attempting to reconnect. This should be greater than the timeout set up on the server side, the idea being that if the server doesn't send a connection response within this time then there has probably been a network failure and we should reconnect. When the client reconnects, any existing channel subscriptions are re-established automatically.

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');

Transports

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.

Using the backend

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 at yoursite.com/faye and the client script at 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.

Regarding the mount parameter: Faye will respond to any path under the mount point, so /faye, /faye.js and /faye/some/subpath will all be handled by Faye rather than your application. This is so that Faye can interoperate with clients that use different URLs for different message types.

Usage examples and a demo app are in the examples directory.

Node.js backend

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);

Node.js client

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', {timeout: 120});

// 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.

Rack backend

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.

Rack client

Again mirroring the Node tools, Faye has a server-side Ruby client that can be used under EventMachine. Setup is identical:

# Remote client
client = Faye::Client.new('http://example.com/faye', :timeout => 120)

# Local client
server = Faye::RackAdapter.new(options)
client = server.get_client

Both types of client 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're using the RackAdapter as middleware and running your app using Thin, applications further down the chain can get a client for it from the Rack environment, for example in a Sinatra application:

get '/post' do
  env['faye.client'].publish('/mentioning/*', {
    'user' => 'sinatra',
    'message' => params[:message]
  })
  params[:message]
end

Development

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 hoe

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.

To-do

  • Investigate WebSockets as a possible message transport

  • Support Bayeux extensions for authentication

  • Let local server-side clients listen to /meta/* channels

  • Provide support for user-defined /service/* channels

  • Allow server to scale to multiple nodes

License

(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.

Something went wrong with that request. Please try again.