Ruby
Article
By Dhaivat Pandya

WebSockets in the Ruby Ecosystem

By Dhaivat Pandya
Ruby is learning to love WebSockets

Ruby is learning to love WebSockets

What the heck is a “WebSocket”, exactly? Some of us have heard about the changes that are coming to Rails with regard to WebSockets (e.g. Action Cable in Rails 5) but it’s a bit difficult to pinpoint exactly what WebSockets are meant to do. What problem do they solve and how do they fit into the realm of HTTP, the web, and Ruby? That’s what we’ll cover in this article.

Why?

Let’s dial the time machine to the beginning of the web. Way back in the day, as we all know, websites consisted of static pages with a bunch of links between them. These pages are called “static” because nothing about them really changes on a per-user sort of basis. The server just serves up the same thing to every single user based on the path that the user requests. We quickly realized that this sort of thing was all well and good if all we wanted the web to be was the equivalent of an easily available book, but we could actually do a lot more. So, with input elements and CGI (Common Gateway Interface – a way for external scripts to talk to the web server), dynamic elements creeped into web pages.

Now, we could actually process input data and do something with it. As websites got busier, we realized that CGI was pretty terrible at scaling. Along came a slew of options such as FastCGI to remedy this problem. We came up with all sorts of frameworks to make writing back-ends a lot easier: Rails, Django, etc. All this progress happened, but at the end of the day, we were still serving up some HTML (through a variety of methods), the user was reading this mostly static HTML and then requesting some different HTML.

Then, developers realized the power of Javascript and communication with the server through AJAX. No longer were pages just blobs of HTML. Instead, Javascript was used to alter the content of these pages based on asynchronous communication with the server. Often, state changes that occurred on the server had to be reflected on the client. Taking a very simple example, maybe we want a notification to show up on the admin panel when the number of users on the server exceeds a certain limit. However, the methods used to do this sort of thing weren’t the best. One common solution was HTTP long polling. With it, the client (i.e. Javascript code running in the browser) sends the HTTP server a request which the server keeps open (i.e. the server doesn’t send any data but doesn’t close the connection) until some kind of update is available.

You might be wondering: Why do we do this waiting stuff if the client could somehow just ask the server to tell the client when an update comes along? Well, unfortunately, HTTP doesn’t really let us do that. HTTP wasn’t designed to be a server-driven protocol. The client sends the requests, the HTTP server answers them. Period. Long polling isn’t really a great solution since it causes all sorts of headaches when it comes to scaling, users switching between Wi-Fi and cellular, etc. How do we solve this problem of letting the server talk to the client?

WebSockets

This is where WebSockets come into the picture. A bunch of very smart people got together and figured out a protocol that could be used for bi-directional communication between the client and server. They then wrote it up in an RFC (i.e. Request for Comments; which are more or less documentation for the various parts of the Internet), specifically RFC 6455. The document defines a TCP-like protocol that allows the server and the client to communicate. The client is given a specific API to talk to the server (known as the WebSocket API). The power of WebSockets becomes obvious when you realize that the client can now create events based on input from the server. Want to update your UI when a stock reaches a certain price? No problem, just have the server tell the client when that price limit is reached and write some event-driven code.

How does all of this stuff work alongside HTTP? Most of it works more or less independently of HTTP. However, the “handshake” has to be carried out over HTTP. If you’re familiar with TCP, you know that a handshake takes place as soon as you open a new connection. The handshake is meant to exchange some basic information and initiate the connection. For WebSockets, the handshake exists to let the server know that it should open up a WebSocket communication channel with the client. HTTP is used to convey this message with a special “HTTP Upgrade” request that has some fields specific to WebSockets.

But, this means that the HTTP server we use has to implement WebSockets, our web framework has to give us some kind of way to work with WebSocket connections, and our Javascript running on the browser (which serves as the client) has to be able to speak WebSocket.

Javascript and WebSockets

The last bit of the problem (Javascript talking WebSocket) comes in the form of WebSocket implementations provided by browser vendors. Recent browser versions include WebSocket support so the technology is a reasonable choice for many situations.

The WebSockets Javascript API has pretty good documentation from MDN. Although we’re focusing mostly on the concepts behind WebSockets, it’s worthwhile to take a quick peek at the Javascript API since most of the knowledge can be re-used on the back-end (remember: WebSockets are bi-directional, so we’d expect the API on both client and server to be very similar). We can create a WebSocket object:

var socket = new WebSocket("ws://domain.com/server", "protocol")

where “protocol” can be replaced by a string representing the protocol which is handled by the given server. Notice that the URL begins with ws://. This is the case for cleartext WebSockets, wss:// can be used for WebSockets over SSL. We can send data too:

socket.send("whatever")

Most importantly, we can receive data from the server:

socket.onmessage = function(ev) {
  console.log(ev.data);
}

So, Javascript support is pretty good. How about on the server side where our Ruby runs?

Ruby and WebSockets

The relationship between real-time messaging and Ruby has been complicated. The Ruby community was taken for quite a spin when Node.js shot out of nowhere and grabbed a ton of mindshare. The reason this happened wasn’t that Node is particularly more pleasant for writing run of the mill web applications. In fact, one could argue that a framework like Express is quite a chore to use in comparison to the more full-featured Rails. But, Node made real-time and event-driven interactions easy. I think Socket.io played a huge rule in this: it was finally easy to make the client and server communicate in a simple, hassle-free manner. For quite a bit of time, Rails was behind the curve when it came to real-time and it still is, to some extent. But, we do have several options that make WebSockets possible in Rails.

Faye

Faye is somewhat of a standard solution when it comes to real-time and Rails. Although it uses something called the Bayeux protocol, recent versions of it also include a standard WebSocket server. In fact, we had a fairly recent series detailing how to put together a real-time application using Faye.

EventMachine

EventMachine is an event processing library for Ruby that also happens to support WebSocket-based communication using em-websocket. It is a pretty simple API that allows us to put together real-time communication pretty quickly. EngineYard has a nice blog post on using WebSockets with EventMachine.

Outsourcing

This technique is a bit of a controversy. The idea is that even if you use Rails for most of your application, you don’t have to use it for the real-time messaging components of your application. For this, you could basically anything (say, Node) and run it under a different subdomain. There are a number of reasons why I dislike this approach. Generally, context-switching between two different languages gets incredibly annoying, especially since the real-time vs. non-real-time portions of your application are probably intimately connected. Secondly, code duplication comes for free using this approach, since you may have to reimplement some of your models in Node in order to serve up the right kind of real-time data. There are a few situations in which this sort of thing makes sense, however. If you have a largely traditional web application with only one or two very simple real-time components, it might be reasonable to implement these in Node.

ActionCable

This is the new kid in the block who hasn’t quite moved in yet. ActionCable is one of the new features of Rails 5 and, basically, allows us to use WebSockets within Rails without any hassle. However, we don’t have a ton of information on what ActionCable will turn out to be when Rails 5 is put in our hands (Fall 2015). I definitely believe that ActionCable can serve to rejuvenate interest in Rails within the web development community. Rails has been eclipsed by a ton of activity in the Javascript/Node world for a bit especially as real-time and SPAs have become the name of the game. Hopefully that will change.

Wrapping It Up

WebSockets are a pretty big deal because they alter the way we think about the client-server relationship when it comes to HTTP. In a sense, they blur the distinction between the client and the server, fixing the balance between the two. As more and more browsers implement the WebSockets standard, they’ll probably become crucial to how we develop web applications. Hopefully this article gave you an idea of how WebSockets came to be and how to use them within the Ruby ecosystem.

  • Incidentally, when making your argument against Node, you also made one of the best arguments for Node :-)

    Any Rails-based solution still uses a combination of Ruby and JavaScript. A Node solution can be *just* JavaScript.

    Not saying all-JavaScript has no problems. But the language-context-switch overhead is why many people cut Ruby out of the picture entirely.

    • dhaivatpandya

      The argument I was making concerned using Node along with Rails in a deployment – that, without question, leads to context-switching. Using only Node is of course a viable solution but wasn’t really the focus of the article.

    • Neo Salivahana

      I don’t believe the author made any argument against the node.

  • Mark T

    I feel the mix of use of both Ruby and Javascript is an ideal.
    Limiting to one language is a risk.
    This does not imply I will ever use node/noddy.

  • RE: Outsourcing

    This is a slightly confusing term as it generally means “not building yourself”. However, you mean “not using Ruby”.

    ## A Dedicated Real-Time Component

    If you have a dedicated real-time component in your architecture it promotes loose coupling. There are pros and cons to this. I’d certainly recommend against duplicating models between application components; if you’re going to push messages onto a message queue to be consumed and distributed by the real-time component then you can do so in a more raw form that can instantly be “pushed” and which will remove duplication. So, you could be using a Ruby based solution such as Faye (Ruby), Node such as Faye (Node) or Socket.IO, a .NET solution like SignalR etc.

    ## Using a Hosted Service

    There’s also the option of outsourcing to hosted services such as Pusher (who I work for) where your integration is generally just a few lines; include the gem, build your message and publish the message (Pusher.trigger(channelName, eventName, data)) and on the client include the script, connect to the service and subscribe + bind to the events (message updates). In this case there’s no language context-switching.

    ## What to consider?

    When using another technology or outsourcing to a 3rd party service you need to consider cost (technology, training and salary), application complexity and where the real-time component fits into your business. For the latter you may feel the real-time component is something you have to own (build) or you may want to focus on business features that deliver value to your customers where a hosted service offers a lot of value.

    • dhaivatpandya

      Thanks for the input, Phil.

      External services are one point I didn’t go into a lot of depth with. But, I think the consideration of training costs definitely applies for switching over part of your app to Javascript. Definitely a great option to consider, especially if you don’t want the headache of running your own Faye server and basically eliminates language context switch.

  • Boaz Segev

    Beautifully written. I do feel you missed the Plezi framework which can run inside a rails application (using the GRHttp server). It’s is a very easy websocket solution which auto-connects with Redis for easy scaling and supports broadcasting and unicasting of messages in a very comfortable way.

    I love it’s ease of use, although I’m somewhat biased as it’s my pet project… it’s fairly fresh, but I would love it if you gave it a look: https://github.com/boazsegev/plezi

  • dhaivatpandya

    Hi, I hadn’t seen the Plezi framework. Definitely looks very interesting. Can you re-use the models from your Rails application within the Plezi application?

    • Boaz Segev

      It’s possible to re-use your models from your Rails/Sinatra app. There are two ways to share the same codebase between an existing Rack app (Rails/Sinatra etc’) and Plezi:

      1. Plezi allows you to run both the Rails/Rack/Sinatra app and the Plezi app in one process, using the GRHttp server (Plezi routes will have priority, so the original app will still respond to 404 errors).

      2. Another option is to use Plezi’s `Placebo` API, which allows you to use Plezi within your Rails app and to use your Rails app within Plezi, syncing both using Redis.

      Both ways allow you share the same codebase. This means that you can use all the models and helpers from your existing application inside the Plezi application and you can push data to Websockets and make Plezi API calls from within your existing application.

      I think the second way is better for scaling while the easier path is to just write a short websocket controller for Plezi and share the same process.

Recommended
Sponsors
Get the latest in Ruby, once a week, for free.