1 O que é o Action Cable?
O Action Cable integra perfeitamente WebSockets com o restante de sua aplicação Rails. Ele permite que recursos em tempo real sejam escritos em Ruby no mesmo estilo e formato do restante de sua aplicação Rails, mantendo ao mesmo tempo desempenho e escalabilidade. É uma oferta de pilha completa que fornece tanto um framework JavaScript do lado do cliente quanto um framework Ruby do lado do servidor. Você tem acesso ao seu modelo de domínio inteiro escrito com Active Record ou seu ORM de escolha.
2 Terminologia
O Action Cable usa WebSockets em vez do protocolo de solicitação-resposta HTTP. Tanto o Action Cable quanto os WebSockets introduzem algumas terminologias menos familiares:
2.1 Conexões
Conexões formam a base do relacionamento cliente-servidor. Um único servidor Action Cable pode lidar com várias instâncias de conexão. Ele tem uma instância de conexão por conexão WebSocket. Um único usuário pode ter várias WebSockets abertas em sua aplicação se ele usar várias guias do navegador ou dispositivos.
2.2 Consumidores
O cliente de uma conexão WebSocket é chamado de consumidor. No Action Cable, o consumidor é criado pelo framework JavaScript do lado do cliente.
2.3 Canais
Cada consumidor pode, por sua vez, se inscrever em vários canais. Cada canal encapsula uma unidade lógica de trabalho, semelhante ao que um controlador faz em uma configuração típica de MVC. Por exemplo, você pode ter um ChatChannel
e um AppearancesChannel
, e um consumidor pode se inscrever em um ou ambos desses canais. No mínimo, um consumidor deve estar inscrito em um canal.
2.4 Assinantes
Quando o consumidor está inscrito em um canal, ele age como um assinante. A conexão entre o assinante e o canal é, surpreendentemente, chamada de assinatura. Um consumidor pode agir como um assinante de um determinado canal várias vezes. Por exemplo, um consumidor pode se inscrever em várias salas de bate-papo ao mesmo tempo. (E lembre-se de que um usuário físico pode ter vários consumidores, um por guia/dispositivo aberto em sua conexão).
2.5 Pub/Sub
Pub/Sub ou Publicar-Inscrever-se refere-se a um paradigma de fila de mensagens em que os remetentes de informações (publicadores) enviam dados para uma classe abstrata de destinatários (assinantes), sem especificar destinatários individuais. O Action Cable usa essa abordagem para se comunicar entre o servidor e muitos clientes.
2.6 Transmissões
Uma transmissão é um link pub/sub onde tudo transmitido pelo transmissor é enviado diretamente para os assinantes do canal que estão transmitindo essa transmissão nomeada. Cada canal pode estar transmitindo zero ou mais transmissões.
3 Componentes do lado do servidor
3.1 Conexões
Para cada WebSocket aceito pelo servidor, um objeto de conexão é instanciado. Este objeto se torna o pai de todas as assinaturas de canal que são criadas a partir de então. A conexão em si não lida com nenhuma lógica de aplicativo específica além da autenticação e autorização. O cliente de uma conexão WebSocket é chamado de consumidor da conexão. Um usuário individual criará um par consumidor-conexão por guia do navegador, janela ou dispositivo que ele tiver aberto.
As conexões são instâncias de ApplicationCable::Connection
, que estende ActionCable::Connection::Base
. Em ApplicationCable::Connection
, você autoriza a conexão de entrada e prossegue para estabelecê-la se o usuário puder ser identificado.
3.1.1 Configuração da conexão
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
Aqui, identified_by
designa um identificador de conexão que pode ser usado para encontrar a conexão específica posteriormente. Observe que qualquer coisa marcada como um identificador criará automaticamente um delegado com o mesmo nome em todas as instâncias de canal criadas a partir da conexão.
Este exemplo depende do fato de que você já tenha lidado com a autenticação do usuário em algum outro lugar de sua aplicação e que uma autenticação bem-sucedida defina um cookie criptografado com o ID do usuário.
O cookie é então enviado automaticamente para a instância de conexão quando uma nova conexão é tentada, e você o usa para definir o current_user
. Ao identificar a conexão com este mesmo usuário atual, você também está garantindo que pode recuperar posteriormente todas as conexões abertas por um determinado usuário (e potencialmente desconectá-las todas se o usuário for excluído ou não autorizado).
Se a sua abordagem de autenticação inclui o uso de uma sessão, você usa o cookie store para a sessão, o cookie da sessão é chamado _session
e a chave do ID do usuário é user_id
, você pode usar esta abordagem:
verified_user = User.find_by(id: cookies.encrypted['_session']['user_id'])
3.1.2 Tratamento de Exceções
Por padrão, exceções não tratadas são capturadas e registradas no logger do Rails. Se você deseja interceptar globalmente essas exceções e reportá-las para um serviço externo de rastreamento de bugs, por exemplo, você pode fazer isso com rescue_from
:
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
rescue_from StandardError, with: :report_error
private
def report_error(e)
SomeExternalBugtrackingService.notify(e)
end
end
end
3.1.3 Callbacks de Conexão
Existem callbacks before_command
, after_command
e around_command
disponíveis para serem invocados antes, depois ou em torno de cada comando recebido por um cliente, respectivamente.
O termo "comando" aqui se refere a qualquer interação recebida por um cliente (inscrever-se, cancelar a inscrição ou realizar ações):
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :user
around_command :set_current_account
private
def set_current_account(&block)
# Agora todos os canais podem usar Current.account
Current.set(account: user.account, &block)
end
end
end
3.2 Canais
Um canal encapsula uma unidade lógica de trabalho, similar ao que um controlador faz em uma configuração típica de MVC. Por padrão, o Rails cria uma classe pai ApplicationCable::Channel
(que estende ActionCable::Channel::Base
) para encapsular a lógica compartilhada entre seus canais.
3.2.1 Configuração do Canal Pai
# app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
Em seguida, você criaria suas próprias classes de canal. Por exemplo, você poderia ter um
ChatChannel
e um AppearanceChannel
:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end
Um consumidor poderia então se inscrever em um ou ambos desses canais.
3.2.2 Inscrições
Os consumidores se inscrevem em canais, atuando como assinantes. Sua conexão é chamada de inscrição. As mensagens produzidas são então roteadas para essas inscrições de canal com base em um identificador enviado pelo consumidor do canal.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Chamado quando o consumidor se torna um assinante deste canal com sucesso.
def subscribed
end
end
3.2.3 Tratamento de Exceções
Assim como ApplicationCable::Connection
, você também pode usar rescue_from
em um
canal específico para lidar com exceções lançadas:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
rescue_from 'MyError', with: :deliver_error_message
private
def deliver_error_message(e)
broadcast_to(...)
end
end
3.2.4 Callbacks de Canal
ApplicationCable::Channel
fornece vários callbacks que podem ser usados para acionar lógica
durante o ciclo de vida de um canal. Os callbacks disponíveis são:
before_subscribe
after_subscribe
(também chamado de:on_subscribe
)before_unsubscribe
after_unsubscribe
(também chamado de:on_unsubscribe
)
NOTA: O callback after_subscribe
é acionado sempre que o método subscribed
é chamado,
mesmo que a inscrição tenha sido rejeitada com o método reject
. Para acionar after_subscribe
apenas em inscrições bem-sucedidas, use after_subscribe :send_welcome_message, unless: :subscription_rejected?
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
after_subscribe :send_welcome_message, unless: :subscription_rejected?
after_subscribe :track_subscription
private
def send_welcome_message
broadcast_to(...)
end
def track_subscription
# ...
end
end
4 Componentes do Lado do Cliente
4.1 Conexões
Os consumidores requerem uma instância da conexão em seu lado. Isso pode ser estabelecido usando o seguinte JavaScript, que é gerado por padrão pelo Rails:
4.1.1 Conectar Consumidor
// app/javascript/channels/consumer.js
// Action Cable fornece o framework para lidar com WebSockets no Rails.
// Você pode gerar novos canais onde recursos do WebSocket vivem usando o comando `bin/rails generate channel`.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
Isso irá preparar um consumidor que se conectará a /cable
em seu servidor por padrão.
A conexão não será estabelecida até que você também tenha especificado pelo menos uma inscrição
que você está interessado em ter.
O consumidor pode opcionalmente receber um argumento que especifica a URL para se conectar. Isso pode ser uma string ou uma função que retorna uma string que será chamada quando o WebSocket for aberto.
// Especificar uma URL diferente para se conectar
createConsumer('wss://example.com/cable')
// Ou ao usar websockets sobre HTTP
createConsumer('https://ws.example.com/cable')
// Use uma função para gerar dinamicamente a URL
createConsumer(getWebSocketURL)
function getWebSocketURL() {
const token = localStorage.get('auth-token')
return `wss://example.com/cable?token=${token}`
}
4.1.2 Assinante
Um consumidor se torna um assinante criando uma inscrição em um determinado canal:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "AppearanceChannel" })
Embora isso crie a inscrição, a funcionalidade necessária para responder a dados recebidos será descrita posteriormente. Um consumidor pode agir como um assinante de um determinado canal várias vezes. Por exemplo, um consumidor pode se inscrever em várias salas de bate-papo ao mesmo tempo:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
5 Interações Cliente-Servidor
5.1 Streams
Streams fornecem o mecanismo pelo qual os canais roteiam o conteúdo publicado (transmissões) para seus assinantes. Por exemplo, o seguinte código usa stream_from
para se inscrever na transmissão chamada chat_Best Room
quando o valor do parâmetro :room
é "Best Room"
:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
Em seguida, em outro lugar em sua aplicação Rails, você pode transmitir para essa sala chamando broadcast
:
ActionCable.server.broadcast("chat_Best Room", { body: "Esta sala é a melhor sala." })
Se você tiver um stream relacionado a um modelo, o nome da transmissão pode ser gerado a partir do canal e do modelo. Por exemplo, o seguinte código usa stream_for
para se inscrever em uma transmissão como posts:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
, onde Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
é o GlobalID do modelo Post.
class PostsChannel < ApplicationCable::Channel
def subscribed
post = Post.find(params[:id])
stream_for post
end
end
Você pode então transmitir para este canal chamando broadcast_to
:
PostsChannel.broadcast_to(@post, @comment)
5.2 Transmissões
Uma transmissão é um link pub/sub onde qualquer coisa transmitida por um publicador é roteada diretamente para os assinantes do canal que estão transmitindo essa transmissão nomeada. Cada canal pode estar transmitindo zero ou mais transmissões.
As transmissões são puramente uma fila online e dependente do tempo. Se um consumidor não estiver transmitindo (inscrito em um determinado canal), ele não receberá a transmissão caso se conecte posteriormente.
5.3 Assinaturas
Quando um consumidor está inscrito em um canal, ele age como um assinante. Essa conexão é chamada de assinatura. As mensagens recebidas são então roteadas para essas assinaturas de canal com base em um identificador enviado pelo consumidor de cabo.
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
5.4 Passando Parâmetros para Canais
Você pode passar parâmetros do lado do cliente para o lado do servidor ao criar uma assinatura. Por exemplo:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
Um objeto passado como primeiro argumento para subscriptions.create
se torna o hash de parâmetros no canal de cabo. A palavra-chave channel
é obrigatória:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
# Em algum lugar do seu aplicativo isso é chamado, talvez
# de um NewCommentJob.
ActionCable.server.broadcast(
"chat_#{room}",
{
sent_by: 'Paul',
body: 'Este é um aplicativo de bate-papo legal.'
}
)
5.5 Rebroadcasting de uma Mensagem
Um caso de uso comum é rebroadcastar uma mensagem enviada por um cliente para qualquer outro cliente conectado.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
def receive(data)
ActionCable.server.broadcast("chat_#{params[:room]}", data)
end
end
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
// data => { sent_by: "Paul", body: "Este é um aplicativo de bate-papo legal." }
}
}
chatChannel.send({ sent_by: "Paul", body: "Este é um aplicativo de bate-papo legal." })
O rebroadcast será recebido por todos os clientes conectados, incluindo o cliente que enviou a mensagem. Observe que os parâmetros são os mesmos que quando você se inscreveu no canal.
6 Exemplos Full-Stack
As seguintes etapas de configuração são comuns a ambos os exemplos:
6.1 Exemplo 1: Aparências de Usuários
Aqui está um exemplo simples de um canal que rastreia se um usuário está online ou não e em qual página eles estão. (Isso é útil para criar recursos de presença, como mostrar um ponto verde ao lado de um nome de usuário se eles estiverem online).
Crie o canal de aparência do lado do servidor:
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
def subscribed
current_user.appear
end
def unsubscribed
current_user.disappear
end
def appear(data)
current_user.appear(on: data['appearing_on'])
end
def away
current_user.away
end
end
Quando uma assinatura é iniciada, o callback subscribed
é acionado e aproveitamos essa oportunidade para dizer "o usuário atual realmente apareceu". Essa API de aparecer/desaparecer pode ser suportada pelo Redis, um banco de dados ou qualquer outra coisa.
Crie a assinatura do canal de aparecimento no lado do cliente:
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("AppearanceChannel", {
// Chamado uma vez quando a assinatura é criada.
initialized() {
this.update = this.update.bind(this)
},
// Chamado quando a assinatura está pronta para uso no servidor.
connected() {
this.install()
this.update()
},
// Chamado quando a conexão WebSocket é fechada.
disconnected() {
this.uninstall()
},
// Chamado quando a assinatura é rejeitada pelo servidor.
rejected() {
this.uninstall()
},
update() {
this.documentIsActive ? this.appear() : this.away()
},
appear() {
// Chama `AppearanceChannel#appear(data)` no servidor.
this.perform("appear", { appearing_on: this.appearingOn })
},
away() {
// Chama `AppearanceChannel#away` no servidor.
this.perform("away")
},
install() {
window.addEventListener("focus", this.update)
window.addEventListener("blur", this.update)
document.addEventListener("turbo:load", this.update)
document.addEventListener("visibilitychange", this.update)
},
uninstall() {
window.removeEventListener("focus", this.update)
window.removeEventListener("blur", this.update)
document.removeEventListener("turbo:load", this.update)
document.removeEventListener("visibilitychange", this.update)
},
get documentIsActive() {
return document.visibilityState === "visible" && document.hasFocus()
},
get appearingOn() {
const element = document.querySelector("[data-appearing-on]")
return element ? element.getAttribute("data-appearing-on") : null
}
})
6.1.1 Interação Cliente-Servidor
Cliente se conecta ao Servidor via
createConsumer()
. (consumer.js
). O Servidor identifica essa conexão pelocurrent_user
.Cliente se inscreve no canal de aparecimento via
consumer.subscriptions.create({ channel: "AppearanceChannel" })
. (appearance_channel.js
)Servidor reconhece que uma nova assinatura foi iniciada para o canal de aparecimento e executa seu callback
subscribed
, chamando o métodoappear
emcurrent_user
. (appearance_channel.rb
)Cliente reconhece que uma assinatura foi estabelecida e chama
connected
(appearance_channel.js
), que por sua vez chamainstall
eappear
.appear
chamaAppearanceChannel#appear(data)
no servidor e fornece um hash de dados{ appearing_on: this.appearingOn }
. Isso é possível porque a instância do canal no lado do servidor automaticamente expõe todos os métodos públicos declarados na classe (exceto os callbacks), para que eles possam ser acessados como chamadas de procedimento remoto através do métodoperform
de uma assinatura.Servidor recebe a solicitação para a ação
appear
no canal de aparecimento para a conexão identificada porcurrent_user
(appearance_channel.rb
). Servidor recupera os dados com a chave:appearing_on
do hash de dados e define-o como o valor para a chave:on
que está sendo passada paracurrent_user.appear
.
6.2 Exemplo 2: Recebendo Novas Notificações Web
O exemplo de aparecimento tratava de expor funcionalidades do servidor para invocação no lado do cliente por meio da conexão WebSocket. Mas a grande coisa sobre WebSockets é que é uma via de mão dupla. Então, agora, vamos mostrar um exemplo em que o servidor invoca uma ação no cliente.
Este é um canal de notificações web que permite acionar notificações web no lado do cliente quando você transmite para os fluxos relevantes:
Crie o canal de notificações web no lado do servidor:
# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
end
Crie a assinatura do canal de notificações web no lado do cliente:
// app/javascript/channels/web_notifications_channel.js
// Lado do cliente que pressupõe que você já solicitou
// a permissão para enviar notificações web.
import consumer from "./consumer"
consumer.subscriptions.create("WebNotificationsChannel", {
received(data) {
new Notification(data["title"], { body: data["body"] })
}
})
Transmita conteúdo para uma instância do canal de notificações web de qualquer outro lugar em seu aplicativo:
# Em algum lugar do seu aplicativo, isso é chamado, talvez de um NewCommentJob
WebNotificationsChannel.broadcast_to(
current_user,
title: 'Coisas novas!',
body: 'Todas as notícias que cabem imprimir'
)
A chamada WebNotificationsChannel.broadcast_to
coloca uma mensagem na fila de pubsub do adaptador de assinatura atual sob um nome de transmissão separado para cada
usuário. Para um usuário com um ID de 1, o nome de transmissão seria
web_notifications:1
.
O canal foi instruído a transmitir tudo o que chega em
web_notifications:1
diretamente para o cliente, invocando o callback received
.
Os dados passados como argumento são o hash enviado como segundo parâmetro
para a chamada de transmissão no lado do servidor, codificado em JSON para a viagem pela rede
e descompactado para o argumento de dados que chega como received
.
6.3 Exemplos Mais Completos
Consulte o repositório rails/actioncable-examples para um exemplo completo de como configurar o Action Cable em um aplicativo Rails e adicionar canais.
7 Configuração
O Action Cable tem duas configurações obrigatórias: um adaptador de assinatura e as origens de solicitação permitidas.
7.1 Adaptador de Assinatura
Por padrão, o Action Cable procura por um arquivo de configuração em config/cable.yml
.
O arquivo deve especificar um adaptador para cada ambiente do Rails. Consulte a
seção Dependências para obter informações adicionais sobre adaptadores.
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: redis://10.10.3.153:6381
channel_prefix: appname_production
7.1.1 Configuração do Adaptador
Abaixo está uma lista dos adaptadores de assinatura disponíveis para os usuários finais.
7.1.1.1 Adaptador Async
O adaptador async é destinado para desenvolvimento/teste e não deve ser usado em produção.
7.1.1.2 Adaptador Redis
O adaptador Redis requer que os usuários forneçam uma URL apontando para o servidor Redis.
Além disso, um channel_prefix
pode ser fornecido para evitar colisões de nomes de canal
quando se usa o mesmo servidor Redis para várias aplicações. Consulte a documentação do Redis Pub/Sub para mais detalhes.
O adaptador Redis também suporta conexões SSL/TLS. Os parâmetros SSL/TLS necessários podem ser passados na chave ssl_params
no arquivo de configuração YAML.
produção:
adaptador: redis
url: rediss://10.10.3.153:tls_port
channel_prefix: appname_production
ssl_params: {
ca_file: "/caminho/para/ca.crt"
}
As opções fornecidas para ssl_params
são passadas diretamente para o método OpenSSL::SSL::SSLContext#set_params
e podem ser qualquer atributo válido do contexto SSL.
Consulte a documentação do OpenSSL::SSL::SSLContext para outros atributos disponíveis.
Se você estiver usando certificados autoassinados para o adaptador Redis atrás de um firewall e optar por pular a verificação do certificado, então o verify_mode
do SSL deve ser definido como OpenSSL::SSL::VERIFY_NONE
.
AVISO: Não é recomendado usar VERIFY_NONE
em produção, a menos que você entenda absolutamente as implicações de segurança. Para definir essa opção para o adaptador Redis, a configuração deve ser ssl_params: { verify_mode: <%= OpenSSL::SSL::VERIFY_NONE %> }
.
7.1.1.3 Adaptador PostgreSQL
O adaptador PostgreSQL usa o pool de conexões do Active Record e, portanto, a
configuração do banco de dados config/database.yml
da aplicação para sua conexão.
Isso pode mudar no futuro. #27214
7.2 Origens de Solicitação Permitidas
Action Cable só aceitará solicitações de origens especificadas, que são passadas para a configuração do servidor como um array. As origens podem ser instâncias de strings ou expressões regulares, contra as quais será feita uma verificação de correspondência.
config.action_cable.allowed_request_origins = ['https://rubyonrails.com', %r{http://ruby.*}]
Para desabilitar e permitir solicitações de qualquer origem:
config.action_cable.disable_request_forgery_protection = true
Por padrão, o Action Cable permite todas as solicitações de localhost:3000 quando executado no ambiente de desenvolvimento.
7.3 Configuração do Consumidor
Para configurar a URL, adicione uma chamada para action_cable_meta_tag
no seu layout HTML
HEAD. Isso usa uma URL ou caminho normalmente definido via config.action_cable.url
nos
arquivos de configuração do ambiente.
7.4 Configuração do Pool de Trabalhadores
O pool de trabalhadores é usado para executar callbacks de conexão e ações de canal em isolamento da thread principal do servidor. O Action Cable permite que a aplicação configure o número de threads processadas simultaneamente no pool de trabalhadores.
config.action_cable.worker_pool_size = 4
Além disso, observe que seu servidor deve fornecer pelo menos o mesmo número de conexões de banco de dados
que você tem trabalhadores. O tamanho padrão do pool de trabalhadores é definido como 4, então
isso significa que você deve disponibilizar pelo menos 4 conexões de banco de dados.
Você pode alterar isso em config/database.yml
através do atributo pool
.
7.5 Registro do Lado do Cliente
O registro do lado do cliente está desabilitado por padrão. Você pode habilitar isso definindo ActionCable.logger.enabled
como true.
import * as ActionCable from '@rails/actioncable'
ActionCable.logger.enabled = true
7.6 Outras Configurações
A outra opção comum a ser configurada são as tags de log aplicadas ao logger de conexão por conexão. Aqui está um exemplo que usa o ID da conta do usuário, se disponível, caso contrário, "no-account" enquanto marca:
config.action_cable.log_tags = [
-> request { request.env['user_account_id'] || "no-account" },
:action_cable,
-> request { request.uuid }
]
Para uma lista completa de todas as opções de configuração, consulte a
classe ActionCable::Server::Configuration
.
8 Executando Servidores Cable Autônomos
O Action Cable pode ser executado como parte da sua aplicação Rails ou como um servidor autônomo. No desenvolvimento, executar como parte do aplicativo Rails geralmente é bom, mas em produção você deve executá-lo como um servidor autônomo.
8.1 No Aplicativo
O Action Cable pode ser executado junto com sua aplicação Rails. Por exemplo, para
ouvir solicitações WebSocket em /websocket
, especifique esse caminho para
config.action_cable.mount_path
:
# config/application.rb
class Application < Rails::Application
config.action_cable.mount_path = '/websocket'
end
Você pode usar ActionCable.createConsumer()
para se conectar ao servidor de cabo
se action_cable_meta_tag
for invocado no layout. Caso contrário, um caminho é
especificado como primeiro argumento para createConsumer
(por exemplo, ActionCable.createConsumer("/websocket")
).
Para cada instância do servidor que você cria e para cada trabalhador que seu servidor inicia, você também terá uma nova instância do Action Cable, mas o adaptador Redis ou PostgreSQL mantém as mensagens sincronizadas entre as conexões.
8.2 Autônomo
Os servidores de cabo podem ser separados do seu servidor de aplicação normal. É ainda uma aplicação Rack, mas é sua própria aplicação Rack. A configuração básica recomendada é a seguinte: ```ruby
cable/config.ru
require_relative "../config/environment" Rails.application.eager_load!
run ActionCable.server ```
Em seguida, para iniciar o servidor:
bundle exec puma -p 28080 cable/config.ru
Isso inicia um servidor de cabo na porta 28080. Para informar ao Rails para usar este servidor, atualize sua configuração:
# config/environments/development.rb
Rails.application.configure do
config.action_cable.mount_path = nil
config.action_cable.url = "ws://localhost:28080" # use wss:// in production
end
Por fim, certifique-se de ter configurado o consumidor corretamente.
8.3 Notas
O servidor WebSocket não tem acesso à sessão, mas tem acesso aos cookies. Isso pode ser usado quando você precisa lidar com autenticação. Você pode ver uma maneira de fazer isso com o Devise neste artigo.
9 Dependências
Action Cable fornece uma interface de adaptador de assinatura para processar suas
internos de pubsub. Por padrão, os adaptadores assíncronos, inline, PostgreSQL e Redis
são incluídos. O adaptador padrão
em novas aplicações Rails é o adaptador assíncrono (async
).
O lado Ruby das coisas é construído em cima de websocket-driver, nio4r e concurrent-ruby.
10 Implantação
Action Cable é alimentado por uma combinação de WebSockets e threads. Tanto o encanamento do framework quanto o trabalho do canal especificado pelo usuário são tratados internamente por utilizando o suporte nativo de threads do Ruby. Isso significa que você pode usar todos os seus modelos Rails existentes sem problemas, desde que você não tenha cometido nenhum pecado de segurança de thread.
O servidor Action Cable implementa a API de sequestro de soquete do Rack, permitindo assim o uso de um padrão multithread para gerenciar conexões internamente, independentemente de o servidor de aplicativos ser multithread ou não.
Consequentemente, o Action Cable funciona com servidores populares como Unicorn, Puma e Passenger.
11 Testando
Você pode encontrar instruções detalhadas sobre como testar a funcionalidade do Action Cable no guia de testes.
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua se encontrar algum erro de digitação ou factual. Para começar, você pode ler nossa contribuição à documentação seção.
Você também pode encontrar conteúdo incompleto ou desatualizado. Por favor, adicione qualquer documentação ausente para o principal. Certifique-se de verificar Guias Edge primeiro para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.
Se por algum motivo você encontrar algo para corrigir, mas não puder corrigi-lo você mesmo, por favor abra uma issue.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem-vinda no Fórum oficial do Ruby on Rails.