I have got Action Cable working in a local host environment and in this situation I start the Puma server using a simple file containing
# /bin/bash
bundle exec puma -p 28080 cable/config.ru
Once this happens the puma server starts and is listening to this 28080 port and the port the local server is running on. Through hunting online I couldn't find a place that would tell me a way to emulate this on heroku or a way to have my server always start on the same port (though I don't know if that would give me the desired result either)
I have a javascript file set up to create a consumer related to that port.
//= require cable
//= require_self
//= require_tree .
this.App = {};
App.cable = Cable.createConsumer('ws://127.0.0.1:28080');
I imagine I'll need to change the 127.0.0.1 part as well for the deploy to heroku to work as well but I'm not certain. I attempted to cut off the 28080 part and replace it with ENV['PORT'] but it said it was an unknown variable even though I have a puma.rb file set up which has its port set as
... (only part of the file)
rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
...
So it seemed to me that ENV['PORT'] was being defined as when I check the heroku logs the puma server will be
2015-07-26T06:50:25.278030+00:00 heroku[web.1]: Starting process with command `bin/rails server -p 48875 -e production`
2015-07-26T06:50:30.760680+00:00 app[web.1]: => Booting Puma
2015-07-26T06:50:30.760714+00:00 app[web.1]: => Rails 4.2.1 application starting in production on http://0.0.0.0:48875
2015-07-26T06:50:30.760716+00:00 app[web.1]: => Run `rails server -h` for more startup options
2015-07-26T06:50:30.760718+00:00 app[web.1]: => Ctrl-C to shutdown server
2015-07-26T06:50:31.578843+00:00 app[web.1]: Puma 2.12.2 starting...
2015-07-26T06:50:31.578851+00:00 app[web.1]: * Min threads: 0, max threads: 16
2015-07-26T06:50:31.578859+00:00 app[web.1]: * Environment: production
2015-07-26T06:50:31.578861+00:00 app[web.1]: * Listening on tcp://0.0.0.0:48875
I apologize if anything is unclear and would be happy to provide any more information if I left anything out.
EDIT
Here is the updated code in /app/assets/javascripts/channels/index.js.erb
//= require cable
//= require_self
//= require_tree .
this.App = {};
App.cable = Cable.createConsumer('<%= ENV["CABLE_SERVER"] %>');
where ENV["CABLE_SERVER"] points to ws://the-action-cable-server.herokuapp.com
. This variable is stored in the rails server env variables.
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
andactioncable
.Create a
.buildpacks
file in the root of your project with this:On
rails
andactioncable
, create an env varBUILDPACK_URL
withhttps://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
andProcfile.actioncable
.In
Procfile.rails
, you describe all the needed dynos except the action cable server, eg.:In
Procfile.actioncable
, you describe only the action cable server: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 chooseconfig/puma/config.rb
.config/puma.rb
is the default location when you start puma without any specific config file, which is what we have inProcfile.actioncable
. This can lead to unexpected behaviors (see the comments below).Now, on
rails
, create an env varPROCFILE_PATH
withProcfile.rails
and onactioncable
, create an env varPROCFILE_PATH
withProcfile.actioncable
.Speaking of env vars, the
actioncable
needs all the env vars fromrails
that are necessary to start the actioncable server like the DATABASE_URL or any credentials.Now the crucial step: how do we connect
rails
andactioncable
together? This is simply done by using the same Redis instance.rails
will put messages on Redis andactioncable
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 setREDIS_URL
with the same value onrails
andactioncable
. Here is the config file for the cable servercable.yml
: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.This
CABLE_SERVER
variable can now point tows://127.0.0.1:28080
locally and onrails
, the value will be the url ofactioncable
.Now you're ready to deploy the code on
rails
andactioncable
.Caveats/Drawbacks
actioncable
, if you have any client authentication, you can't usecookies
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.Alternatives
hth