Matt Hughes tinkerer, grower, craftsman

Ruby-flavored adventures with WebSockets and Faye

I've been working more with Ruby lately, and wanted to implement a Ruby real-time messaging project like the one in my previous post. I decided on Faye, because its features seemed to match Socket.IO's pretty closely (subscriptions and transport fallbacks, in particular).

I initially ran into some confusion after working with Socket.IO, mostly because I had the false impression that Socket.IO was a synonym for WebSockets. I was wrong about that!

Socket.IO != WebSockets

WebSockets are only a method of transporting data over the web, and both Socket.IO and Faye built a WebSocket-compatible framework on top of it. These frameworks include subscribing to certain messages or events, as well as fallbacks for browsers without WebSocket support.

Faye was more complicated to get up and running than Socket.IO, but that could be attributed to my level of Ruby knowledge. It requires separate instance of Ruby, while you can serve Socket.IO on the same instance of Node.js as your application. You also have to run Faye with thin in production mode.

It was also a bit more difficult to see what was going on behind-the-scenes with the Faye browser client. In particular, I wanted to verify that Faye was using the WebSockets, intead of long-polling or another fallback.

It was easy to see the Socket.IO client's socket.opts.transports property in Firebug, but it took me a lot of searching to figure out how to see the Faye client's detailed information. I finally came across a forum thread that mentioned using Faye.logger = window.console. You wouldn't use this in a live environment, but it lets you see detailed output about the Faye client for debugging.

Enough talk, here's some code

faye.ru

require 'faye'

Faye::WebSocket.load_adapter('thin')
faye = Faye::RackAdapter.new(:mount => '/faye_server', :timeout => 5)

run faye

Run the following command to start the Faye instance: rackup faye.ru -s thin -E production

app.rb

require 'sinatra'
require 'eventmachine'
require 'faye'

client = Faye::Client.new('http://localhost:9292/faye_server')

post '/user/:id' do
  # do work here

  message = client.publish("/messages/#{params[:id]}", "UserUpdated")

  content_type :json
  { "message": "User update accepted" }.to_json
end

Run this Sinatra API: ruby app.rb

index.html

<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="http://localhost:9292/faye_server/client.js"></script>
<script>
  $(document).ready(function() {
    var client = new Faye.Client('http://localhost:9292/faye_server');

    var user = '12345';

    client.subscribe('/messages/' + user, function(data) {
      console.log("Faye: " + data);
      //perform an action based on the data content          
    });        
  });
</script>

Finally, go to index.html in your web browser and open up the JS console. Using your favorite REST client, send a POST to http://localhost:4567/user/12345. You should see a Faye: UserUpdated message show up in your browser's JS console.