Deploying Ruby on Rails app to Heroku while using Action Cable (Puma port listening)

笑着哭i 提交于 2019-12-04 14:02:41
David

Issue

There are some limits to the Heroku router: it will only listen to ports 80 and 443. In other words, you can't open a fixed port on any Heroku application. In the ActionCable server case, there is no way to open a fixed port and get the websocket traffic routed to it. So either Heroku allows such things (which I doubt) or we use a workaround.

Workaround

As of version 0.0.3 of actioncable, here is the workaround I used.

The idea is to have not one Heroku application but two: one for the main rails server and one for the ActionCable server. Both would run on port 80 (or 443).

To run two different servers from one codebase is simply a matter of having two Procfiles: one for rails and one for action cable. There is a buildpack to handle this. To use it, you also need the multi buildpack.

Let's say you have your two Heroku applications called rails and actioncable.

Create a .buildpacks file in the root of your project with this:

https://github.com/cantino/heroku-selectable-procfile
https://github.com/heroku/heroku-buildpack-ruby

On rails and actioncable, create an env var BUILDPACK_URL with https://github.com/heroku/heroku-buildpack-multi.git

Now for the Procfiles, I choose to keep Procfile for running everything locally with foreman and create two custom ones: Procfile.rails and Procfile.actioncable.

In Procfile.rails, you describe all the needed dynos except the action cable server, eg.:

web: bundle exec puma -C config/puma/config.rb
clockwork: bundle exec clockwork lib/clockwork.rb
worker: bundle exec rake jobs:work

In Procfile.actioncable, you describe only the action cable server:

web: bundle exec puma -p $PORT cable/config.ru

Note that we are using a web dyno which will mount the action cable server on port 80 (or 443).

CAUTION You need to move the puma config file config/puma.rb to a custom location. I choose config/puma/config.rb. config/puma.rb is the default location when you start puma without any specific config file, which is what we have in Procfile.actioncable. This can lead to unexpected behaviors (see the comments below).

Now, on rails, create an env var PROCFILE_PATH with Procfile.rails and on actioncable, create an env var PROCFILE_PATH with Procfile.actioncable.

Speaking of env vars, the actioncable needs all the env vars from rails that are necessary to start the actioncable server like the DATABASE_URL or any credentials.

Now the crucial step: how do we connect rails and actioncable together? This is simply done by using the same Redis instance. rails will put messages on Redis and actioncable will listen to them and act accordingly. That's why both have to target the same Redis instance. If you using Heroku Redis, you just need to set REDIS_URL with the same value on rails and actioncable. Here is the config file for the cable server cable.yml:

production: &production
  :url: <%= ENV["REDIS_URL"] %>
  :timeout: 1
development: &development
  :url: <%= ENV["REDIS_URL"] %>
  :timeout: 1
  :inline: true
test: *development

The final step is changing the javascript file so that we can control where is the Action Cable server. We can do this by using an env var.

Change the .js suffix into .js.erb if necessary.

//= require cable
//= require_self
//= require_tree .


this.App = {};

App.cable = Cable.createConsumer('<%= ENV["CABLE_SERVER"] %>');

This CABLE_SERVER variable can now point to ws://127.0.0.1:28080 locally and on rails, the value will be the url of actioncable.

Now you're ready to deploy the code on rails and actioncable.

Caveats/Drawbacks

  • on actioncable, if you have any client authentication, you can't use cookies like in the examples. You have now two Heroku apps and they can't share a cookie. I guess you can still workaround this using a cookie for multiple subdomains.
  • you have now two deploy targets to maintain.
  • you have multiple Procfiles to maintain.
  • hopefully, with time, there will be an easier workaround :)

Alternatives

  • We can have the same server handling the normal web traffic and the websocket traffic using a simple middleware. Here is how.
  • As of rails 5 beta2, the action cable server can now also be side-mounted along with your main rails app. Having two different servers can still make sense: you can scale them individually.

hth

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!