Rails ActionCable 中 "Unable to find subscription with identifier" 的原因可能是什么?
What can be the reason of "Unable to find subscription with identifier" in Rails ActionCable?
我正在使用 Rails 5.0.0.rc1 + ActionCable + Redis 构建一个 Messenger 应用程序。
我只有一个频道 ApiChannel
并且里面有很多动作。有一些 "unicast" 个动作 -> 请求一些东西,得到一些东西,还有 "broadcast" 个动作 -> 做一些事情,将有效负载广播给一些连接的客户端。
有时我从这里收到 RuntimeError
异常:https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/connection/subscriptions.rb#L70 Unable to find subscription with identifier (...)
.
这可能是什么原因?在什么情况下我可以获得这样的异常?我花了很多时间来调查这个问题(并将继续这样做),我们将不胜感激任何提示!
它似乎与这个问题有关:https://github.com/rails/rails/issues/25381
当 Rails 回复订阅已创建但实际上尚未完成时出现某种竞争条件。
作为临时解决方案,在建立订阅后添加一个小的超时已经解决了这个问题。
不过,还需要做更多的调查。
此错误的原因可能是您订阅的标识符和发送消息的标识符不同。我在 Rails 5 API 模式下使用 ActionCable(gem 'devise_token_auth'),我也遇到了同样的错误:
订阅(错误):
{"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
帮助器:(你不应该公开普通标识符 - 以相同的方式对它们进行编码 Rails 将普通 cookie 编码为已签名的 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 命令。两个命令必须具有相同的标识符散列(此处为 "channel")。这里有趣的是,不需要 subscribed
挂钩(!)——即使没有它,你仍然可以发送消息(在 SUBSCRIBE 之后)(但没有人会收到它们——没有 subscribed
挂钩)。
另一个有趣的地方是在 subscribed
挂钩中我使用了这段代码:
stream_from "unread_#{unread_chanel_token}_channel"
显然 unread_chanel_token
可以是任何东西 - 它仅适用于 "receiving" 方向。
因此 订阅 标识符(如 \"channel\":\"UnreadChannel\"
)必须被视为 "password" 以供将来 消息发送 操作(例如,它仅适用于 "sending" 方向)- 如果你想发送消息,(首先发送订阅,然后)再次提供相同的 "pass",否则你将得到描述的错误。
还有更多 - 它实际上只是一个 "password" - 如您所见,您实际上可以将消息发送到任何您想要的地方:
ActionCable.server.broadcast "unread_#{unread_chanel_token}_channel", sender_id: current_user.id
很奇怪吧?
这一切都非常复杂。为什么在official documentation中没有描述?
我正在使用 Rails 5.0.0.rc1 + ActionCable + Redis 构建一个 Messenger 应用程序。
我只有一个频道 ApiChannel
并且里面有很多动作。有一些 "unicast" 个动作 -> 请求一些东西,得到一些东西,还有 "broadcast" 个动作 -> 做一些事情,将有效负载广播给一些连接的客户端。
有时我从这里收到 RuntimeError
异常:https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/connection/subscriptions.rb#L70 Unable to find subscription with identifier (...)
.
这可能是什么原因?在什么情况下我可以获得这样的异常?我花了很多时间来调查这个问题(并将继续这样做),我们将不胜感激任何提示!
它似乎与这个问题有关:https://github.com/rails/rails/issues/25381
当 Rails 回复订阅已创建但实际上尚未完成时出现某种竞争条件。
作为临时解决方案,在建立订阅后添加一个小的超时已经解决了这个问题。
不过,还需要做更多的调查。
此错误的原因可能是您订阅的标识符和发送消息的标识符不同。我在 Rails 5 API 模式下使用 ActionCable(gem 'devise_token_auth'),我也遇到了同样的错误:
订阅(错误):
{"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
帮助器:(你不应该公开普通标识符 - 以相同的方式对它们进行编码 Rails 将普通 cookie 编码为已签名的 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 命令。两个命令必须具有相同的标识符散列(此处为 "channel")。这里有趣的是,不需要 subscribed
挂钩(!)——即使没有它,你仍然可以发送消息(在 SUBSCRIBE 之后)(但没有人会收到它们——没有 subscribed
挂钩)。
另一个有趣的地方是在 subscribed
挂钩中我使用了这段代码:
stream_from "unread_#{unread_chanel_token}_channel"
显然 unread_chanel_token
可以是任何东西 - 它仅适用于 "receiving" 方向。
因此 订阅 标识符(如 \"channel\":\"UnreadChannel\"
)必须被视为 "password" 以供将来 消息发送 操作(例如,它仅适用于 "sending" 方向)- 如果你想发送消息,(首先发送订阅,然后)再次提供相同的 "pass",否则你将得到描述的错误。
还有更多 - 它实际上只是一个 "password" - 如您所见,您实际上可以将消息发送到任何您想要的地方:
ActionCable.server.broadcast "unread_#{unread_chanel_token}_channel", sender_id: current_user.id
很奇怪吧?
这一切都非常复杂。为什么在official documentation中没有描述?