Rails 中的 WebSockets:在使用 websockets 时,我们是否必须在现有应用程序中创建一个新的 WebSocketController?
WebSockets in Rails : Do we have to create a new WebSocketController in our existing application while using the websockets?
我有一个 WebSockets 的聊天应用程序 here . I was going through the wiki。 ChatController 中的代码是这样描述的:
class ChatController < WebsocketRails::BaseController
def initialize_session
# perform application setup here
controller_store[:message_count] = 0
end
end
我的问题:我应该如何在扩展 ApplicationController
的 chatcontroller
中实现这个? .
我是应该创建一个新的控制器来使用 websockets
还是修改现有的 chatcontroller
来扩展 WebsocketRails
? .我是 WebSockets 的新手,所以任何关于这方面的帮助都会很有帮助。
谢谢。
关于 websocket-rails gem, as well as Faye and Plezi 的答案是肯定的,您必须创建一个新的控制器 class,与 Rails 使用的控制器不同。
你的websocket-rails必须继承WebsocketRails::BaseController
,而你的Rails控制器必须继承ActionController::Base
(通常通过继承你的ApplicationController,它继承这个class).
Ruby 不支持双重 class 继承(尽管使用模块时可以使用 Mixin)。
另一方面,Faye 没有以相同的面向对象方式使用控制器,那里有更多选项。例如,您可以将 websocket 事件映射到您的控制器 CLASS 方法,但是您可能在为每个 websocket 连接初始化 Rails 控制器时遇到问题,因为控制器的某些内部机制可能会中断。例如,会话信息将不可用,您可能必须避免任何和所有 Rails 特定方法。
使用 Plezi,这些继承问题不会存在,但 Plezi 会自动创建 Http 路由到您的控制器拥有的任何 public 方法 - 因此您的 Rails 方法将以您没有的方式公开'打算。另一方面,Plezi 控制器可以响应 Http 和 Websockets。
我还写关于 Faye 和 Plezi 的原因是因为 websocket-rails gem 最后一次更新(根据 CHANGELOG)是在 2014 年 3 月[=71] =]...
我不知道它能在 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 链接可能会扰乱您脚本的初始化,请将脚本添加到您的 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 的聊天应用程序 here . I was going through the wiki。 ChatController 中的代码是这样描述的:
class ChatController < WebsocketRails::BaseController
def initialize_session
# perform application setup here
controller_store[:message_count] = 0
end
end
我的问题:我应该如何在扩展 ApplicationController
的 chatcontroller
中实现这个? .
我是应该创建一个新的控制器来使用 websockets
还是修改现有的 chatcontroller
来扩展 WebsocketRails
? .我是 WebSockets 的新手,所以任何关于这方面的帮助都会很有帮助。
谢谢。
关于 websocket-rails gem, as well as Faye and Plezi 的答案是肯定的,您必须创建一个新的控制器 class,与 Rails 使用的控制器不同。
你的websocket-rails必须继承WebsocketRails::BaseController
,而你的Rails控制器必须继承ActionController::Base
(通常通过继承你的ApplicationController,它继承这个class).
Ruby 不支持双重 class 继承(尽管使用模块时可以使用 Mixin)。
另一方面,Faye 没有以相同的面向对象方式使用控制器,那里有更多选项。例如,您可以将 websocket 事件映射到您的控制器 CLASS 方法,但是您可能在为每个 websocket 连接初始化 Rails 控制器时遇到问题,因为控制器的某些内部机制可能会中断。例如,会话信息将不可用,您可能必须避免任何和所有 Rails 特定方法。
使用 Plezi,这些继承问题不会存在,但 Plezi 会自动创建 Http 路由到您的控制器拥有的任何 public 方法 - 因此您的 Rails 方法将以您没有的方式公开'打算。另一方面,Plezi 控制器可以响应 Http 和 Websockets。
我还写关于 Faye 和 Plezi 的原因是因为 websocket-rails gem 最后一次更新(根据 CHANGELOG)是在 2014 年 3 月[=71] =]...
我不知道它能在 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 链接可能会扰乱您脚本的初始化,请将脚本添加到您的 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>
完成。