Причиной этой ошибки может быть различие идентификаторов, на которые вы подписываетесь, и на которые вы обмениваетесь сообщениями. Я использую ActionCable в режиме API Rails 5 (с gem 'devise_token_auth'), и я тоже столкнулся с той же ошибкой:
ПОДПИСАТЬСЯ (ОШИБКА):
{"command":"subscribe","identifier":"{\"channel\":\"UnreadChannel\"}"}
ОТПРАВИТЬ СООБЩЕНИЕ (ОШИБКА):
{"command":"message","identifier":"{\"channel\":\"UnreadChannel\",\"correspondent\":\"[email protected]\"}","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\":\"[email protected]\",\"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
Подводя итог всему этому, вы должны сначала вызвать команду SUBSCRIBE, если вы хотите позже вызвать команду MESSAGE. Обе команды должны иметь одинаковый хеш-идентификатор (здесь «канал»). Что интересно, хук subscribed
не требуется (!) - даже без него можно отправлять сообщения (после SUBSCRIBE) (но их никто не получит - без хука 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
Странно, да?
Это все довольно сложно. Почему это не описано в официальной документации?
person
prograils
schedule
18.04.2017