0

We've setup Cloudfront in front of our application, but unfortunately it strips the Upgrade header required for ActionCable to run.

We'd like to have a different subdomain that points to the same servers, but bypasses Cloudfront (socket.site.com, for instance). We've done this and it's somewhat working, but it seems like a persistent connection can't be made. ActionCable continues to retry to make the connection every 10s and seems unable to hold the connection open:

enter image description here

Any advice related to Cloudfront or different domains for ActionCable is appreciated.

Kieran E
  • 3,616
  • 2
  • 18
  • 41

1 Answers1

2

To all who follow, hopefully this helps.

As of the time of me writing this (Oct. 2018), it doesn't appear that you can use ActionCable behind Cloudfront at all. CF will discard the upgrade header which will prevent a secure socket connection from ever being made.

Our setup was CF -> Application Load Balancer (ALB) -> EC2. On the AWS side, we began by making a subdomain (socket.example.com) that pointed directly to the same ALB and bypassed CF entirely. Note that Classic Load Balancers absolutely will not work. You can only use ALBs.

This alone did not fix the issue. On your Rails config, you have to add the following lines to your production.rb:

config.action_cable.url = 'wss://socket.example.com:28080/cable'
config.action_cable.allowed_request_origins = ['https://example.com'] # Not the subdomain

You may also need to update your CSP to include wss://socket.example.com/cable for connect_src.

If at this point you're getting a message about failing to upgrade, you need to ensure that your NGINX config is correct. This answer may help.


You will also need to reflect this change in your cable.js. This following snippet works for me with local development as well as production, but you may need to alter it. I wrote it with pre-ES6 in mind because this file never hit Babel in our configuration.

(function() {
  this.App || (this.App = {})

  var wsUrl
  if(location.host.indexOf('localhost') != -1) {
    wsUrl = '/cable'
  } else {
    var host = location.host
    var protocol = location.protocol
    wsUrl = protocol + '//socket.' + host + '/cable'
  }

  App.cable = ActionCable.createConsumer(wsUrl)

}).call(this)

That may be all you need, depending on your authentication scheme. However, I was using cookies shared between the main application and ActionCable and this caused a difficult bug. The connection would appear to be made correctly, but it would actually fail and ActionCable would retry every 10s. The final step was to ensure the auth cookies being set would work across the socket subdomain. I updated my cookie as such:

cookies.signed[:cookie_name] = {
  value: payload,
  domain: ['.socket.example.com', '.example.com']
  # Some people have to specify tld_length, but I was fine without it
}
Kieran E
  • 3,616
  • 2
  • 18
  • 41
  • I've recently been playing with render.com, they're using Cloudflare (not Cloudfront) and I haven't had issues with the connection upgrade. Cloudflare may be a potential solution for some folks (if this is still an issue). – tgf Jun 02 '23 at 05:57