class ActionCable::Channel::Base
Action Cable Base
Канал предоставляет базовую структуру для объединения поведения в логические единицы при обмене данными через соединение WebSocket. Канал можно представить как разновидность контроллера, который, помимо простого ответа на прямые запросы подписчика, может отправлять ему содержимое.
Channel — долгоживущие объекты. Объект канала создаётся, когда потребитель кабельного соединения становится подписчиком, и существует до тех пор, пока потребитель не отключится. Это может длиться секунды, минуты, часы и даже дни. Поэтому нужно позаботиться о том, чтобы не делать в канале ничего неразумного, что могло бы резко увеличить объём занимаемой им памяти и так далее. Ссылки существуют постоянно, поэтому они не будут освобождены, как обычно происходит с экземпляром контроллера, который удаляется после каждого запроса.
Долгоживущие каналы (и соединения) также означают, что вы отвечаете за актуальность данных. Если вы храните ссылку на запись пользователя, а имя изменилось, пока эта ссылка сохранялась, вы можете отправлять устаревшие данные, если не примете меры предосторожности.
Преимущество долгоживущих экземпляров каналов в том, что с помощью переменных экземпляра можно хранить ссылки на объекты, с которыми смогут взаимодействовать будущие запросы подписчиков. Вот простой пример:
class ChatChannel < ApplicationCable::Channel
def subscribed
@room = Chat::Room[params[:room_number]]
end
def speak(data)
@room.speak data, user: current_user
end
end
Действие speak просто использует объект Chat::Room, созданный при первой подписке потребителя на канал, когда подписчик хочет что-то сказать в комнате.
Обработка действий
В отличие от подклассов ActionController::Base, действия каналов не подчиняются ограничениям RESTful. Вместо этого Action Cable работает по модели удалённого вызова процедур. В канале можно объявить любой открытый метод (необязательно принимающий аргумент data), и этот метод автоматически становится доступным для вызова клиентом.
Пример:
class AppearanceChannel < ApplicationCable::Channel
def subscribed
@connection_token = generate_connection_token
end
def unsubscribed
current_user.disappear @connection_token
end
def appear(data)
current_user.appear @connection_token, on: data['appearing_on']
end
def away
current_user.away @connection_token
end
private
def generate_connection_token
SecureRandom.hex(36)
end
end
В этом примере методы subscribed и unsubscribed недоступны для вызова, поскольку они уже объявлены в ActionCable::Channel::Base, а appear и away доступны. Метод generate_connection_token также недоступен для вызова, поскольку он является закрытым. Обратите внимание, что appear принимает параметр data, который затем используется при вызове модели. Метод away не принимает его, поскольку это просто действие-триггер.
Также обратите внимание, что в этом примере доступен current_user, поскольку он был отмечен как идентифицирующий атрибут соединения. Для всех таких идентификаторов в экземпляре канала автоматически создаётся делегирующий метод с тем же именем.
Отклонение запросов на подписку
Канал может отклонить запрос на подписку в обратном вызове subscribed, вызвав метод reject:
class ChatChannel < ApplicationCable::Channel
def subscribed
@room = Chat::Room[params[:room_number]]
reject unless current_user.can_access?(@room)
end
end
В этом примере подписка будет отклонена, если у current_user нет доступа к комнате чата. На стороне клиента обратный вызов Channel#rejected будет вызван, когда сервер отклонит запрос на подписку.
Атрибуты
Открытые методы класса
# File actioncable/lib/action_cable/channel/base.rb, line 128
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
methods = (public_instance_methods(true) -
# Except for public instance methods of Base and its ancestors
ActionCable::Channel::Base.public_instance_methods(true) +
# Be sure to include shadowed public instance methods of this class
public_instance_methods(false) -
# Except the internal methods
internal_methods).uniq
methods.map!(&:name)
methods.to_set
end
end Список имён методов, которые следует считать действиями. В него входят все открытые методы экземпляра канала, кроме внутренних методов (определённых в Base), а также добавляются любые внутренние методы, которые при этом существуют непосредственно в классе.
Возвращает
-
Set— набор всех методов, которые следует считать действиями.
# File actioncable/lib/action_cable/channel/base.rb, line 163
def initialize(connection, identifier, params = {})
@connection = connection
@identifier = identifier
@params = params
# When a channel is streaming via pubsub, we want to delay the confirmation
# transmission until pubsub subscription is confirmed.
#
# The counter starts at 1 because it's awaiting a call to #subscribe_to_channel
@defer_subscription_confirmation_counter = Concurrent::AtomicFixnum.new(1)
@reject_subscription = nil
@subscription_confirmation_sent = nil
@unsubscribed = false
delegate_connection_identifiers
end Закрытые методы класса
# File actioncable/lib/action_cable/channel/base.rb, line 148 def clear_action_methods! # :doc: @action_methods = nil end
action_methods кэшируются, и иногда возникает необходимость обновить их. Метод ::clear_action_methods! позволяет это сделать, чтобы при следующем вызове action_methods они были пересчитаны.
# File actioncable/lib/action_cable/channel/base.rb, line 153 def method_added(name) # :doc: super clear_action_methods! end
Обновляет кэшированный action_methods при добавлении нового action_method.
Открытые методы экземпляра
# File actioncable/lib/action_cable/channel/base.rb, line 184
def perform_action(data)
action = extract_action(data)
if processable_action?(action)
payload = { channel_class: self.class.name, action: action, data: data }
ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do
dispatch_action(action, data)
end
else
logger.error "Unable to process #{action_signature(action, data)}"
end
end Извлекает имя действия из переданных данных и обрабатывает его через канал. Обработка гарантирует, что запрошенное действие является открытым методом канала, объявленным пользователем (то есть не одним из обратных вызовов, например subscribed).
# File actioncable/lib/action_cable/channel/base.rb, line 199
def subscribe_to_channel
run_callbacks :subscribe do
subscribed
end
reject_subscription if subscription_rejected?
ensure_confirmation_sent
end Этот метод вызывается после добавления подписки к соединению и подтверждает или отклоняет её.
Закрытые методы экземпляра
# File actioncable/lib/action_cable/channel/base.rb, line 258 def defer_subscription_confirmation! # :doc: @defer_subscription_confirmation_counter.increment end
# File actioncable/lib/action_cable/channel/base.rb, line 262 def defer_subscription_confirmation? # :doc: @defer_subscription_confirmation_counter.value > 0 end
# File actioncable/lib/action_cable/channel/base.rb, line 252 def ensure_confirmation_sent # :doc: return if subscription_rejected? @defer_subscription_confirmation_counter.decrement transmit_subscription_confirmation unless defer_subscription_confirmation? end
# File actioncable/lib/action_cable/channel/base.rb, line 270 def reject # :doc: @reject_subscription = true end
# File actioncable/lib/action_cable/channel/base.rb, line 226 def subscribed # :doc: # Override in subclasses end
Вызывается, когда потребитель становится подписчиком канала. Обычно здесь настраиваются потоки, которые канал будет отправлять подписчику.
# File actioncable/lib/action_cable/channel/base.rb, line 266 def subscription_confirmation_sent? # :doc: @subscription_confirmation_sent end
# File actioncable/lib/action_cable/channel/base.rb, line 274 def subscription_rejected? # :doc: @reject_subscription end
# File actioncable/lib/action_cable/channel/base.rb, line 239
def transmit(data, via: nil) # :doc:
logger.debug do
status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}"
status += " (via #{via})" if via
status
end
payload = { channel_class: self.class.name, data: data, via: via }
ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
connection.transmit identifier: @identifier, message: data
end
end Передаёт подписчику хеш данных. Хеш автоматически оборачивается в JSON-конверт с корректным идентификатором канала в качестве получателя.
# File actioncable/lib/action_cable/channel/base.rb, line 232 def unsubscribed # :doc: # Override in subclasses end
Вызывается, когда потребитель разрывает кабельное соединение. Может использоваться для очистки соединений, отметки пользователей как находящихся не в сети и тому подобного.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.