Rails 5 action cable SSL in production - WebSocket connection failed: Error during WebSocket handshake: Unexpected response 301
Asked Answered
P

4

15

I am trying to run an app with rails 5.0.0beta3 and websockets. I have everything working locally on development but in production I getting this response in my browser's console:

"WebSocket connection failed: Error during WebSocket handshake: Unexpected response 301"

Here is a my nginx conf.

upstream app {
  server unix:/home/dev/workspace/my_app/tmp/sockets/thin.0.sock max_fails=1 fail_timeout=15s;
  server unix:/home/dev/workspace/my_app/tmp/sockets/thin.1.sock max_fails=1 fail_timeout=15s;
}

server {
  listen 443 ssl;
  server_name www.my_app.co;

  root /home/dev/workspace/my_app/public;
  try_files $uri/index.html $uri @app;

  location @app {
    proxy_next_upstream error timeout http_502 http_503;
    proxy_read_timeout 60s;

    proxy_pass http://app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-FORWARDED-PROTO $scheme;
    proxy_redirect off;
  }

  location /websocket/ {
    proxy_pass http://localhost:28080/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;

  ssl on;
  ssl_certificate /ssl/www.my_app.co.crt;
  ssl_certificate_key /ssl/www.my_app.co.key;

  ssl_session_timeout 5m;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
  ssl_prefer_server_ciphers on;
}

I am running thin for my app server and running puma for the websockets server along with redis locally. I am attempting to follow the action cable example app here: https://github.com/rails/actioncable-examples.

I am starting my puma server like this: bundle exec puma -p 28080 cable/config.ru

With this puma.rb in config:

workers 1
threads 1, 10

app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"

rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env

stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true

pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"

Here is the relevant parts of my production.rb config:

config.action_cable.allowed_request_origins = ['https://www.chatben.co', 'https://45.55.192.195']
config.action_cable.url = "wss://www.chatben.co/websocket"
config.force_ssl = false

Here is my development.rb config:

config.action_cable.url = "ws://localhost:28080"
config.action_cable.allowed_request_origins = ['http://localhost:3000']
config.action_cable.disable_request_forgery_protection = true

In my app, I start my cable like this:

@App = {}
App.cable = ActionCable.createConsumer()

Any suggestions would be awesome. I have noticed someone here: RoR 5.0.0 ActionCable wss WebSocket handshake: Unexpected response code: 301 was able to solve this by using a separate domain. That is what I will probably try next but I was hoping it wouldn't come to that.

Thanks in advance for any help! I really appreciate it.

Perusse answered 13/3, 2016 at 19:16 Comment(0)
F
40

The problem is likely that Rails is forcing your connection on to ssl. Since nginx terminates the ssl connection, you need to set the X-Forwarded-Proto header to let Rails know all is good. Here's a full config that works for me:

  location /cable {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto https;
    proxy_redirect off;
  }
Fatigue answered 14/3, 2016 at 8:50 Comment(3)
Hey! That worked! Thanks a ton! Now am I running a Puma server for my app and websockets server. Before I was trying thin for my app server and puma as websockets stand alone server. I am not sure what the trade offs are here, but I am just pretty happy with it working without Passenger. Thanks again!Perusse
@Perusse check this issue github.com/websocket-rails/websocket-rails/issues/151, since puma is not EventMachine based you either have to use a event machine based rack server or run puma in stand alone modeTortious
As of now the location has changed to /-/cable and you could set the header with $scheme as value, otherwise it works perfectly.Diplomatist
P
1

I couldn't figure out my previous problem so I ended up deploying with passenger instead. I still wonder why every request had a 301, but hey, at least I have websockets in production now!

EDIT: After reading troelskn's answer above I was able to deploy a Puma server without needing Passenger.

Perusse answered 14/3, 2016 at 2:7 Comment(1)
Do you still get 301 ?Nyaya
T
1

I updated my answer basically it was pretty easy for me which I couldn't realize earlier. I just did this in my production.rb file after configuring SSL like normal way in nginx conf file.

config.action_cable.allowed_request_origins = [#domains]

It just did everything and was connecting properly.

Thaothapa answered 9/4, 2017 at 11:46 Comment(0)
K
0

for websocket-rails i have to do this in NGINX:

location /websocket{
    proxy_pass http://localhost:3001/websocket;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";    
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto https;
    proxy_redirect off;
  }

and pass false on the javascript to use http:// instead of ws://

var websocket = new WebSocketRails(window.location.hostname + '/websocket', false);
Kleenex answered 12/7, 2016 at 21:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.