我正在使用Rails 5.0.0.rc1 + ActionCable + Redis构建一个messenger应用程序。
我有单一频道ApiChannel
以及其中的一些操作。有一些“单播”行动 - >要求某事,得到回报,并“广播”行动 - >做一些事情,将有效载荷广播到一些连接的客户端。
我不时会从此处获得RuntimeError
例外:https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/connection/subscriptions.rb#L70 Unable to find subscription with identifier (...)
。
这可能是什么原因?在什么情况下我可以得到这样的例外?我花了很多时间来调查这个问题(并将继续这样做),任何提示都将不胜感激!
答案 0 :(得分:1)
看起来它与此问题有关:https://github.com/rails/rails/issues/25381
当Rails回复订阅时已经创建了某种竞争条件,但事实上它还没有完成。
作为一种临时解决方案,在建立订阅后添加一个小超时解决了这个问题。
但是,还需要进行更多的调查。
答案 1 :(得分:1)
此错误的原因可能是您订阅和发送消息的标识符的差异。我在Rails 5 API模式下使用ActionCable(使用gem' devise_token_auth')我也遇到了同样的错误:
SUBSCRIBE(ERROR):
{"command":"subscribe","identifier":"{\"channel\":\"UnreadChannel\"}"}
发送消息(错误):
{"command":"message","identifier":"{\"channel\":\"UnreadChannel\",\"correspondent\":\"client2@example.com\"}","data":"{\"action\":\"process_unread_on_server\"}"}
出于某种原因,ActionCable要求您的客户端实例两次应用相同的标识符 - 订阅时和发送消息时:
<强> /var/lib/gems/2.3.0/gems/actioncable-5.0.1/lib/action_cable/connection/subscriptions.rb:74 强>
def find(data)
if subscription = subscriptions[data['identifier']]
subscription
else
raise "Unable to find subscription with identifier: #{data['identifier']}"
end
end
这是一个实例:我实现了一个消息传递子系统,用户可以在实时模式下获取未读消息通知。在订阅时,我真的不需要correspondent
,但在发送消息的时候 - 我这样做。
因此解决方案是将correspondent
从标识符哈希移动到数据哈希:
发送消息(正确):
{"command":"message","identifier":"{\"channel\":\"UnreadChannel\"}","data":"{\"correspondent\":\"client2@example.com\",\"action\":\"process_unread_on_server\"}"}
这样就错了。
这是我的UnreadChannel
代码:
class UnreadChannel < ApplicationCable::Channel
def subscribed
if current_user
unread_chanel_token = signed_token current_user.email
stream_from "unread_#{unread_chanel_token}_channel"
else
# http://api.rubyonrails.org/classes/ActionCable/Channel/Base.html#class-ActionCable::Channel::Base-label-Rejecting+subscription+requests
reject
end
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def process_unread_on_server param_message
correspondent = param_message["correspondent"]
correspondent_user = User.find_by email: correspondent
if correspondent_user
unread_chanel_token = signed_token correspondent
ActionCable.server.broadcast "unread_#{unread_chanel_token}_channel",
sender_id: current_user.id
end
end
end
helper :(你不应该公开普通的标识符 - 对它们进行编码,就像Rails将普通的cookie编码为签名的一样)
def signed_token string1
token = string1
# http://vesavanska.com/2013/signing-and-encrypting-data-with-tools-built-in-to-rails
secret_key_base = Rails.application.secrets.secret_key_base
verifier = ActiveSupport::MessageVerifier.new secret_key_base
signed_token1 = verifier.generate token
pos = signed_token1.index('--') + 2
signed_token1.slice pos..-1
end
总而言之,如果您想稍后调用MESSAGE命令,则必须首先调用SUBSCRIBE命令。两个命令必须具有相同的标识符哈希(此处为&#34; channel&#34;)。有趣的是,subscribed
钩子不是必需的(!) - 即使没有它,你仍然可以发送消息(在SUBSCRIBE之后)(但没有人会收到它们 - 没有subscribed
钩子)。
另一个有趣的观点是,在subscribed
钩子里面我使用了这段代码:
stream_from "unread_#{unread_chanel_token}_channel"
显然unread_chanel_token
可以是任何东西 - 它只适用于&#34;接收&#34;方向。
因此订阅标识符(如\"channel\":\"UnreadChannel\"
)必须被视为&#34;密码&#34;对于未来消息发送操作(例如,它仅适用于&#34;发送&#34;方向) - 如果要发送消息,(首先发送订阅,然后)提供相同&#34;通过&#34;再一次,或者你会得到所描述的错误。
而且更多 - 它只是一个密码&#34; - 正如您所看到的,您实际上可以将消息发送到您想要的任何地方:
ActionCable.server.broadcast "unread_#{unread_chanel_token}_channel", sender_id: current_user.id
很奇怪,对吧?
这一切都非常复杂。为什么没有在official documentation中描述?