Action Cable Part II: Deploying Action Cable to Heroku

In my previous post, we set up a basic chatting application using Action Cable. This post will server as the walk-through for deploying that app to Heroku.

You may be familiar with the fact that Rails and Heroku and Websockets don't necessarily get along. A programming pun:

if Rails && Websockets && Heroku  
  use middleware
else  
  have_a_headache
end  

Accordingly, we'll be using a neat piece of middleware written by one of the Rails core-contributors in order to get our Action Cable chatting app up and running on Heroku.

Our middleware will be responsible for holding open a socket connection through which Action Cable can stream and broadcast data.

Creating a Heroku App

Start by creating a Heroku app with heroku create in your terminal in the root directory of this project.

Action Cable Middleware

This piece of middleware comes courtesy of Jorge Bejar, a contributor to Rails. Let's set it up.

Define your middleware here:

# middleware/action_cable_chat.rb

class ChatActionCable  
  def initialize(app, options={})
    @app = app
  end

  def call(env)
    if Faye::WebSocket.websocket?(env)
      ActionCable.server.call(env)
    else
      @app.call(env)
    end
  end
end  

Mount your middleware in production.rb

config.middleware.use ChatActionCable  
config.web_socket_server_url = "wss://pacific-chamber-3660.herokuapp.com/"  

How does this work? Your middleware will intercept all incoming requests to the app. It will introspect on that request to determine if it is a websocket request. If so, it will call on the Action Cable server.

We mount this middleware in production.rb and we set the websocket server URL to a secure websocket server (wss) running on a separate process within our main Puma server.

Now, we need to edit the labs.js file, which establishes the connection to Action Cable on the client side. Change labs.js to labs.js.erb and edit it in the following manner:

this.App = {};

App.cable = Cable.createConsumer("<%= Rails.application.config.web_socket_server_url %>");  

Now that we have our middleware up and running, we need to configure Redis for Heroku.

Configuring Redis

First, provision the Redis addon to your Heroku app.

In your terminal, in the top level of this project:

heroku addons:create redistogo

It should return to you your Redis URL. Something like:

redis://redistogo:18998e348ac30ca0002879ce9d85daf0bb0@tarpon.redistogo.com:10272

Now we need to edit config/redis/cable.yml

development: &development  
  :url: redis://localhost:6379
  :host: localhost
  :port: 6379
  :timeout: 1
  :inline: true
test: *development  
production: &production  
  :url: redis://redistogo:10e348ac30ca0002879ce9d85daf0bb0@tarpon.redistogo.com:10272
  :host: tarpon.redistogo.com
  :port: 10272
  :password: 10e348ac30ca0002879ce9d85daf0bb0
  :inline: true
  :timeout: 1

Your Redis host can be found between the @ and the : of your Redis URL and your Redis port is the collection of digits following the :. To get your password, you can use this trick:

  • Drop into the Heroku Rails console with heroku run rails c
  • Execute:
    • uri = URI.parse(<your redis URL>)
    • uri.password

Last but not least, we need to edit our Procfile.

Procfile

Remove the Redis and Action Cable lines from your Procfile. Instead, it should handle only connecting to a multi-threaded Puma server.

web: bundle exec puma -p $PORT  

And that's it! Push up to Heroku and you should be good to go!

For further reference, you can checkout the repo for this project here. Make sure you are looking at the "heroko-version" branch.

Update

This post shows ActionCable being run in a separate server. However! With Rails 5, the default will be to run ActionCable inside the Rails app process under a sub-URI. To see an example of a deployment using this recommendation, check out this excellent Go Rails post on deploying Action Cable with Passenger. And thanks to @honglilai for pointing out some of these changes.

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus