ActionCable - 在生产环境中无法升级到WebSocket

27

在生产环境下,ActionCable无法工作。在开发环境下运行良好,但在生产环境下无法正常工作。

在Ubuntu 14.04上使用Nginx和Puma运行。我已经检查过redis-server是否正常运行。

Rails版本为5.0.0.1。

production.log

INFO -- : Started GET "/cable/"[non-WebSocket] for 178.213.184.193 at 2016-11-25 14:55:39 +0100
ERROR -- : Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: close, HTTP_UPGRADE: )
INFO -- : Finished "/cable/"[non-WebSocket] for 178.213.184.193 at 2016-11-25 14:55:39 +0100

客户端发来的请求:

GET ws://mityakoval.com/cable HTTP/1.1
Host: mityakoval.com
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://mityakoval.com
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4,uk;q=0.2,nb;q=0.2
Cookie: _vaktdagboka_session=******
Sec-WebSocket-Key: *******
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: actioncable-v1-json, actioncable-unsupported

响应:

HTTP/1.1 404 Not Found
Server: nginx/1.4.6 (Ubuntu)
Date: Fri, 25 Nov 2016 13:52:21 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: no-cache
X-Request-Id: d6374238-69ef-476e-8fc5-e2f8bbb663de
X-Runtime: 0.002500

nginx.conf:

upstream puma {
  server unix:///home/mityakoval/apps/vaktdagboka/shared/tmp/sockets/vaktdagboka-puma.sock;
}

server {
  listen 80 default_server deferred;
  # server_name example.com;

  root /home/mityakoval/apps/vaktdagboka/current/public;
  access_log /home/mityakoval/apps/vaktdagboka/current/log/nginx.access.log;
  error_log /home/mityakoval/apps/vaktdagboka/current/log/nginx.error.log info;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @puma;
  location @puma {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    proxy_pass http://puma;
  }

  location /cable {
    proxy_pass http://puma;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 10M;
  keepalive_timeout 10;
}

cable.yml:

redis: &redis
  adapter: redis
  url: redis://127.0.0.1:6379

production: *redis

development:
  adapter: async

test:
  adapter: async

production.rb文件中:

config.action_cable.allowed_request_origins = ["http://mityakoval.com"]

routes.rb文件中:

mount ActionCable.server, at: '/cable'

更新:

别忘了重启nginx :) 那就是我的问题所在。


你找到任何解决方案了吗? - Developer
@RameshKumarThiyagarajan 你重启了nginx吗? - mityakoval
提醒其他人注意:如果您的nginx设置为多站点服务器,则配置文件可能是/etc/nginx/sites-enabled/sitename而不是/etc/nginx/sites-available/default。 - Dave Collins
你是如何解决这个问题的? - Krupa Suthar
@KrupaSuthar 很抱歉回复晚了。在我的情况下,我只需要重新启动nginx即可。 - mityakoval
10个回答

21

你应该将proxy_pass属性的值从http://puma更改为http://puma/cable

因此,/cable的正确location部分将是:

location /cable {
  proxy_pass http://puma/cable;
  proxy_http_version 1.1;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $http_host;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";
}

那也正是我的问题。 - Иван Бишевац

9

虽然其他帖子已经正确地发布了解决方案,但我认为我应该额外发布一些关于如何确定问题所在/在哪里修复的更多信息,以供其他nginx新手参考。

如果rails错误包含HTTP_UPGRADE:,则您将知道需要在挂载action cable的路径处使用proxy_set_header Upgrade的nginx配置。 (意味着没有传递给HTTP_UPGRADE)。 解决问题后,我的日志显示HTTP_UPGRADE:websocket

  • 注意1:正如操作者提到的那样,请确保在更改后重新启动nginx(我一开始做错了)。

  • 注意2:还要查找nginx配置文件中的include语句,因为您的配置可能会分散在多个文件中。 在我的情况下,location /cable {部分应该位于server {之内,但由于包含语句在不同的配置文件中,我一直没有注意到这一点。

  • 类似的错误但是不同的问题:您的rails日志将在OP提到的一个错误之前包含另一个错误,表明源不被允许,此时您的rails配置需要更新,另一个答案提到需要更新config.action_cable.allowed_request_origins。

日志记录可能因rails而异,但希望这有助于澄清问题以及我作为一个对nginx一无所知的人遇到的一些注意事项。


谢谢,在我的情况下有许多服务器{块,我已经将它放在了监听端口443的一个上,然后它开始工作了。 - Shoaib

5
需要进行 NGINX 配置更改以接受此 Action Cable 请求的分辨率。
location / {
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
}

将上述行添加到nginx站点配置的位置块中,然后重新启动nginx。

4

很晚才参加这个对话,然而,对于任何使用Rails5、Action Cable等等和DEVISE面临相同错误信息的人,你可以像这里建议的那样简单地解决它。问题在于Web套接字服务器没有会话,因此出现了错误消息。

app/channels/application_cable/connection.rb

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      logger.add_tags 'ActionCable', current_user.name
    end

    protected
      def find_verified_user
        verified_user = User.find_by(id: cookies.signed['user.id'])
        if verified_user && cookies.signed['user.expires_at'] > Time.now
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

app/config/initializers/warden_hooks.rb

Warden::Manager.after_set_user do |user,auth,opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = user.id
  auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
end
Warden::Manager.before_logout do |user, auth, opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = nil
  auth.cookies.signed["#{scope}.expires_at"] = nil
end

这个解决方案是由Greg Molnar开发的。


1
你可以直接使用 env['warden'].user 来获取用户对象 :) - desheikh
没错。@desheikh - Georg Keferböck
客户端为Safari时,似乎无法设置cookie。有什么想法吗? - Micah
@Micah,我在Safari上遇到了类似的问题,你有找到更多相关信息吗? - opensource-developer

2
我的解决方案是在我的production.rb文件中添加以下行:
  config.action_cable.url = 'ws://your_site.com/your_action_cable'
  config.action_cable.allowed_request_origins = [ 'http://your_site.com' ]

0

关于/cable,您可以更改您的nginx配置

proxy_set_header X-Forwarded-Proto http;

我使用了您的nginx配置,并在我的服务器上添加了这个更改,它运行良好。


0

我之前使用的是ws://app-url.com/cable

我用了这个url,它可以工作(虽然我为cable禁用了ssl) wss://app-url.com/cable


你的回答可以通过提供更多支持性信息来改善。请编辑以添加进一步细节,例如引用或文档,以便其他人可以确认你的答案是正确的。您可以在帮助中心中找到有关编写良好答案的更多信息。 - Community

0

使用过:

location ^~ /cable {
  ...
}

位置需要^~


0
使用Rails 7.0.6和Puma 6.4.0,这对我有效。
# .platform/nginx/conf.d/websocket.conf
server {
    listen 80;
    server_name yourdomain.com # || elastic.beanstalk.domain.com;
    location / {
        proxy_pass http://unix:///var/run/puma/my_app.sock;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    location /cable {
        proxy_pass http://unix:///var/run/puma/my_app.sock;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_set_header X-NginX-Proxy true;
        proxy_redirect off;
    }   
}

在config/environments/staging.rb文件中。
config.action_cable.url = wss://api.yourdomain.com/cable
config.action_cable.allowed_request_origins = [https://yourdomain.com]
config.action_cable.disable_request_forgery_protection = true # optional

在config/puma.rb中。
bind 'unix:///var/run/puma/my_app.sock'

在 Procfile 中
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -c 1 -v 

-3

你的cable.yml文件应该长这样:

production:
    adapter: redis
    url: <%=ENV['REDIS_URL']%>

然后你应该在环境中设置这个密钥,应该长成这个样子:

REDIS_URL: 'redis://redistogo:keyblahblahblhblah'

另外,在production.rb文件中,你应该有这个:

config.web_socket_server_url = "wss://YOUR_URL.com/cable"

谢谢您的回答。我在哪里可以获取Redis的密钥? - mityakoval
啊,好的,我明白了。我没有使用RedisToGo。Redis服务器正在我的生产服务器上运行。我猜应该是ws://...而不是wss://...,因为我没有使用SSL。 - mityakoval

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接