WebSockets in Rails 4

Development

Reading Time: 2 minutes

I previously published this article on my personal blog early 2013, but thought I’d share it today with our Codeship readers. Enjoy!

I’ve been using Rails 4 (beta) a lot recently. In a previous post we looked at how ActionController::Live can be used with Server-Sent Events, but the problem with that is that there’s no way for the client to communicate back to the web server. Enter: WebSockets.

The main issue with implementing WebSockets is that they have to keep their connections open for a long period of time, and when you’re only running a small cluster of Rails servers, it eats up potential connections fast. That’s why I was excited to hear about two important developments: concurrency in Rails 4 and Rack Hijack.

Concurrency in Rails 4

Rails 4 is now full concurrent, which means that there is no full-stack lock on a request. That means that if you use a concurrent server like Puma you can handle many requests at a time with a single process.

Even better, you can use ruby Threads inside your Rails app. That’s how ActionController::Live works (for streaming).

What this means for us is that we can use Threads to hold websocket connections open without bogging down our server.

Also, this means that our solution does not use Eventmachine, nor does it implement a reactor in any way. It’s concurrent.

Rack Hijack

Rack Hijack came with Rack 1.5.0 which was released in January 2013. Rack hijack allows you to access the underlying socket of a Rack connection in order to bidirectionally communicate with the client. Since Rails is built on Rack we can grab a handle to the client socket right from a Rails controller.

Sign up for a free Codeship Account

Tubesock

Tubesock is the gem that I made to encapsulate this functionality. It’s very small and new and untested so caveat emptor and all that. At its core it provides a module for Rails controllers and a wrapper method to hijack the rack connection. Then it wraps the ruby gem websocket to handle WebSocket handshakes and frames. Here’s an example of using it in a controller:

class ChatController < ApplicationController
  include Tubesock::Hijack

  def chat
    hijack do |tubesock|
      tubesock.onopen do
        tubesock.send_data "Hello, friend"
      end

      tubesock.onmessage do |data|
        tubesock.send_data message: "You said: #{data}"
      end
    end
  end
end 

Right inside the controller action we can hijack the connection and then use some blocks to send information.

You can check out the Tubesock gem on Github for more information.

Also, there is an example chat application you can run and play with.

Happy hacking!

Discuss this article on HackerNews: https://news.ycombinator.com/item?id=9335584

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • Michele Spina

    Is a new connection a new thread for the server?
    If not, is it event-driven like node.js?
    If yes, is it scalable?

    • Nick Gauthier

      A new connection is a new thread in puma. There’s a setting for the thread pool size. It’s not event driven (that’s the EventMachine approach).

      For scaling, you would need to describe different thread pool limits for different actions. For example, you can have hundreds or thousands of websocket threads, but you would not want hundreds of normal requests to the heavier pages in your app. You would do this with your web server or load balancer.

      • Michele Spina

        Normally I work with web sockets with mono threads servers like Node.js.
        I have the following doubts, If you know the answers please help me :) :

        – A multithread server will be memory bounded with sockets. Each thread will require a minimum amount of memory, when the memory will fill up we will need to scale horizontally. So, at the end, it will be more expensive than a event driven server. [source 1=”http://mmcgrana.github.io/2010/07/threaded-vs-evented-servers.html” language=”:”][/source]
        – All the threads will be quite always inactive, because of the asynchronous nature of web sockets.. Despite this, will the contextswitch switch the threads? If so the server will be very slow, right?

        • Nick Gauthier

          1) yes. The tradeoff is event-driven can’t handle longer running or blocking operations, which are common in rails (sadface). That means you have to async everything. So the tradeoff with my solution is that it is much simpler to use but scales poorer.

          2) The server will balance work across all threads that are working, so it will take longer for requests, but the overall time should be only a little slower due to context switch overhead. Consider when you have a spike on an evented server. Some people get processed first and others must wait a while.

          All in all you’re hitting the multi-thread vs reactor tradeoff argument dead on. That’s the whole point of tubesock: provide a solution in a multithreaded way when you want to make that tradeoff choice and you prefer the costs and benefits of multithreading vs singlethreading.

          • Michele Spina

            First of all thanks for answering my boring questions :) You have been very clear.

            It would be interesting to have some benchmarks to compare node.js vs ruby (tubesock) web sockets.

            For the 1), of course you are right. But with a microservices architecture we can mix many technologies and we can take the advantages of everyones.

          • Nick Gauthier

            Yep. Although benchmarks aren’t the point. For me tubesock is a simplicity > performance choice. otherwise I’d just write a massively threaded app in go (which is what I’m doing nowadays! :-D )

  • jdurand

    Reading the tutorial the idea is pretty cool but seeing your chat method definition I have to ask. How does that actually work. I’ve never seen a method created with a block attached like that. Where did you learn that style. It’s totally cool if it does in fact work but i’m a bit blown away that the code you have for the controller executes without throwing and exception.

  • yafeilee

    Michele Spina

    I think it uses the new feature of Rails 4:

    concurrency & multi-thread.

    It needs a specify web server that support concurrency.

    • Nick Gauthier

      Exactly. Puma works well and passenger does too.

  • Eric

    I like that your method does everything from the web server, it’s super lightweight for simple apps.
    However, for apps that need to scale it’s nice to have that kind of simplicity combined with an external message bus (like Pubnub or Pusher).
    Here’s another simple way to achieve browser-push without Eventmachine: http://edraut.github.io/foreign-office/

  • Pingback: Несколько интересностей и полезностей для веб-разработчика #42 | Zit@i0()