6

我正在使用 Rails 5.0.0.rc1 + ActionCable + Redis 构建一个信使应用程序。

我有单通道ApiChannel和许多动作。有一些“单播”动作 -> 请求某些东西,取回某些东西,以及“广播”动作 -> 做某事,将有效负载广播到一些连接的客户端。

我不时RuntimeError从这里得到异常:https Unable to find subscription with identifier (...) ://github.com/rails/rails/blob/master/actioncable/lib/action_cable/connection/subscriptions.rb#L70 。

这可能是什么原因?在什么情况下我会得到这样的异常?我花了很多时间来调查这个问题(并将继续这样做),任何提示将不胜感激!

4

2 回答 2

1

此错误的原因可能是您订阅的标识符和消息传递给的标识符不同。我在 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,但在消息传递时 - 我需要。

所以解决方案是将correspondentfrom identifier hash移动到data hash:

发送信息(正确):

{"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 编码为签名的相同)

  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 命令。两个命令必须具有相同的标识符散列(此处为“通道”)。这里有趣的subscribed是,不需要钩子(!) - 即使没有它,您仍然可以发送消息(在订阅之后)(但没有人会收到它们 - 没有subscribed钩子)。

另一个有趣的地方是在subscribed钩子里面我使用了这段代码:

stream_from "unread_#{unread_chanel_token}_channel"

显然unread_chanel_token可以是任何东西 - 它仅适用于“接收”方向。

因此,订阅标识符(如\"channel\":\"UnreadChannel\")必须被视为未来消息发送操作的“密码”(例如,它仅适用于“发送”方向) - 如果您想发送消息,(首先发送订阅,然后然后)再次提供相同的“通过”,否则您将收到描述的错误。

还有更多——它实际上只是一个“密码”——正如你所看到的,你实际上可以向任何你想要的地方发送消息:

ActionCable.server.broadcast "unread_#{unread_chanel_token}_channel", sender_id: current_user.id

很奇怪,对吧?

这一切都相当复杂。为什么官方文档中没有描述?

于 2017-04-18T13:42:48.333 回答
1

看起来它与这个问题有关:https ://github.com/rails/rails/issues/25381

Rails 回复订阅时的某种竞争条件已创建但实际上尚未完成。

作为临时解决方案,在建立订阅后添加一个小超时已解决了该问题。

不过,还需要做更多的调查。

于 2016-07-06T11:41:40.847 回答