Rails 中的 WebSockets:在使用 websockets 时,我们是不是必须在现有应用程序中创建一个新的 WebSocketController?

Posted

技术标签:

【中文标题】Rails 中的 WebSockets:在使用 websockets 时,我们是不是必须在现有应用程序中创建一个新的 WebSocketController?【英文标题】:WebSockets in Rails : Do we have to create a new WebSocketController in our existing application while using the websockets?Rails 中的 WebSockets:在使用 websockets 时,我们是否必须在现有应用程序中创建一个新的 WebSocketController? 【发布时间】:2015-11-04 11:59:11 【问题描述】:

我有一个聊天应用程序here。我正在浏览 WebSockets 的wiki。 ChatController 中的代码描述如下:

class ChatController < WebsocketRails::BaseController
  def initialize_session
    # perform application setup here
    controller_store[:message_count] = 0
  end
end

我的问题:我应该如何在我的chatcontroller 中实现这个,它正在扩展ApplicationController ? . 我是否应该创建一个新的控制器来使用websockets 或修改现有的chatcontroller 应该扩展WebsocketRails? .我是 WebSockets 的新手,所以任何有关这方面的帮助都会很有帮助。

谢谢。

【问题讨论】:

而不是 downvoting ,至少要回答一些问题。说明拒绝投票的原因。 【参考方案1】:

对于websocket-rails gem、Faye 和Plezi,答案是肯定的,你必须创建一个新的控制器类,与 Rails 使用的不同。

你的 websocket-rails 必须继承 WebsocketRails::BaseController,而你的 Rails 控制器必须继承 ActionController::Base(通常通过继承你的 ApplicationController,它继承了这个类)。

Ruby 不支持双类继承(尽管使用模块时可以使用 Mixin)。

另一方面,Faye 并没有以相同的面向对象方式使用 Controller,并且那里有更多选项。例如,您可以将 websocket 事件映射到您的控制器 CLASS 方法,但是您可能在为每个 websocket 连接初始化 Rails 控制器时遇到问题,因为某些控制器的内部机制可能会中断。例如,会话信息将不可用,您可能必须避免使用任何和所有 Rails 特定方法。

使用 Plezi,这些继承问题不会消失,但 Plezi 会自动创建 Http 路由到您的 Controller 拥有的任何公共方法 - 因此您的 Rails 方法将以您不希望的方式公开。另一方面,Plezi 控制器可以响应 Http 和 Websocket。

我还写关于 Faye 和 Plezi 的原因是因为 websocket-rails gem 上次更新(根据 CHANGELOG)是在 2014 年 3 月...

我不知道它在 Rails 的最新更新中能否存活(或存活),但我建议继续。

目前,2015 年 11 月,Faye 是更常见的选择,而 Plezi 是该领域的新玩家。我是 Plezi 的作者。

编辑(回复评论)

Faye 和 Plezi 都应该允许一个用户向另一个用户发送消息。

我认为 Plezi 更容易使用,但这是因为我编写了 Plezi。我相信写Faye的人会认为Faye更容易。

有几个关于实现聊天的最佳方式的选项,具体取决于您想要的方式。

如果您安装 Plezi 并运行(在您的终端中),您可以查看 plezi 应用程序演示代码:

  $ plezi mini my_chat

这是将 Plezi websocket 广播添加到现有应用程序的快速技巧。

如果我可以访问您的数据库,我会做一些不同的事情......但作为概念证明已经足够了......现在。

将以下行添加到您的Gemfile

gem 'plezi'

创建一个plezi_init.rb 文件并将其添加到您的config/initializers 文件夹中。这是它现在所拥有的(大部分是破解 Rails cookie,因为我无权访问您的数据库,也无法添加字段):

class WebsocketController

    def on_open
        # this is a Hack - replace this with a database token and a cookie.
        return close unless cookies[:_linkedchats_session] # refuse unauthenticated connections
        # this is a Hack - get the user
        @user_id = decrypt_session_cookie(cookies[:_linkedchats_session].dup)['warden.user.user.key'][0][0].to_s
        puts "#@user_id is connected"
    end

    def on_message data
        # what do you want to do when you get data?        
    end

    protected

    # this will inform the user that a message is waiting
    def message_waiting msg
        write(msg.to_json) if msg[:to].to_s == @user_id.to_s
    end

    # this is a Hack - replace this later
    # use a token authentication instead (requires a database field)
    def decrypt_session_cookie(cookie)
        key ='4f7fad1696b75330ae19a0eeddb236c123727f2a53a3f98b30bd0fe33cfc26a53e964f849d63ad5086483589d68c566a096d89413d5cb9352b1b4a34e75d7a7b'
        cookie = CGI::unescape(cookie)

        # Default values for Rails 4 apps
        key_iter_num = 1000
        key_size     = 64
        salt         = "encrypted cookie"         
        signed_salt  = "signed encrypted cookie"  

        key_generator = ActiveSupport::KeyGenerator.new(key, iterations: key_iter_num)
        secret = key_generator.generate_key(salt)
        sign_secret = key_generator.generate_key(signed_salt)

        encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
        encryptor.decrypt_and_verify(cookie)
    end
end

# IMPORTANT - create the Plezi, route for websocket connections
Plezi.route '/ws', WebsocketController

这几乎就是您需要的所有 Plezi 应用程序。

只需将以下行添加到您的 ChatsController#create 方法中,就在 respond_to 之前:

 WebsocketController.broadcast :message_waiting,
          from: @msg.sender_id,
          to: @msg.receiver_id,
          text: @msg.text,
          msg: :chat

这就是服务器端......现在,客户端。

将以下脚本添加到您的 chat.html.erb 模板中(或者,因为 turbo-links 可能会扰乱您的脚本初始化,请将脚本添加到您的 application.js 文件中......但是您将拒绝很多连接,直到您用户登录):

<script type="text/javascript">

// Your websocket URI should be an absolute path. The following sets the base URI.
// remember to update to the specific controller's path to your websocket URI.
var ws_controller_path = '/ws'; // change to '/controller/path'
var ws_uri = (window.location.protocol.match(/https/) ? 'wss' : 'ws') + '://' + window.document.location.host + ws_controller_path
// websocket variable.
var websocket = NaN
// count failed attempts
var websocket_fail_count = 0
// to limit failed reconnection attempts, set this to a number.
var websocket_fail_limit = NaN


function init_websocket()

    if(websocket && websocket.readyState == 1) return true; // console.log('no need to renew socket connection');
    websocket = new WebSocket(ws_uri);
    websocket.onopen = function(e) 
        // reset the count.
        websocket_fail_count = 0
        // what do you want to do now?
    ;

    websocket.onclose = function(e) 
        // If the websocket repeatedly you probably want to reopen the websocket if it closes
        if(!isNaN(websocket_fail_limit) && websocket_fail_count >= websocket_fail_limit) 
            // What to do if we can't reconnect so many times?
            return
        ;
        // you probably want to reopen the websocket if it closes.
        if(isNaN(websocket_fail_limit) || (websocket_fail_count <= websocket_fail_limit) ) 
            // update the count
            websocket_fail_count += 1;
            // try to reconect
            init_websocket();
        ;
    ;
    websocket.onerror = function(e) 
        // update the count.
        websocket_fail_limit += 1
        // what do you want to do now?
    ;
    websocket.onmessage = function(e) 
        // what do you want to do now?
        console.log(e.data);
        msg = JSON.parse(e.data)
        alert("user id: " + msg.from + " said:\n" + msg.text)
    ;

// setup the websocket connection once the page is done loading
window.addEventListener("load", init_websocket, false); 

</script>

完成。

【讨论】:

感谢您提供详细信息先生,但我开发的应用程序更复杂,因为它在同一页面上与不同用户一个接一个地进行通信。那么,在我的情况下什么会起作用?先生,如果您可以查看我在问题中提到的我的应用程序链接,将对我非常有帮助。之后请建议 websockets 方式或 Faye 或 Plezi 。谢谢你 。 :) 太棒了。但是一个问题是,您提到要在我的chat.html.erb 中添加一些代码,那么我目前拥有的现有代码呢? .意味着它会产生任何类型的冲突吗? .谢谢你:) @huzefabiyawarwala - 没有冲突。您的 Rails 应用程序将消息保存到数据库,Plezi 代码行会将消息作为 websocket 数据发送到客户端。他们一起玩得很好。唯一的问题是,您不能使用thinpuma 或其他服务器。 Plezi 使用iodine 作为它的服务器。 iodine 也可以充当 Rack 服务器,因此您的 Rails 应用程序使用 Rack,而 Plezi 直接使用服务器(这有助于 Http 流式传输、websockets 和更多 Rack 不适合的东西)。 ok 一定会在我完成后更新您,先生。我非常感谢您提供的帮助。 :) 我已经用你提供的代码更新了我的聊天应用程序,但它似乎不起作用,先生,我已经上传了它here。请让我知道我需要进行哪些更正才能使其正常工作。谢谢你 。 :)

以上是关于Rails 中的 WebSockets:在使用 websockets 时,我们是不是必须在现有应用程序中创建一个新的 WebSocketController?的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL 通知和带有 Rails 的 WebSockets

Heroku 上带有 SSL 的 WebSockets - Chrome 和 FireFox 中的问题

在开发环境中使用 SSL/TLS 的 Rails 和 WebSockets

使用 Websockets 在 Rails 应用程序中实现自动保存

Websockets 和 Rails

带有 Rails(Puma)的 Websockets - WebSocket 握手期间出错:意外的响应代码:200