1 ¿Qué es Action Cable?
Action Cable integra de manera transparente los WebSockets con el resto de tu aplicación Rails. Permite escribir características en tiempo real en Ruby de la misma forma y estilo que el resto de tu aplicación Rails, manteniendo un buen rendimiento y escalabilidad. Es una oferta de pila completa que proporciona tanto un framework de JavaScript en el lado del cliente como un framework de Ruby en el lado del servidor. Tienes acceso a todo tu modelo de dominio escrito con Active Record o tu ORM de elección.
2 Terminología
Action Cable utiliza WebSockets en lugar del protocolo de solicitud-respuesta HTTP. Tanto Action Cable como WebSockets introducen terminología menos familiar:
2.1 Conexiones
Conexiones forman la base de la relación cliente-servidor. Un único servidor de Action Cable puede manejar múltiples instancias de conexión. Tiene una instancia de conexión por cada conexión WebSocket. Un único usuario puede tener múltiples WebSockets abiertos en tu aplicación si utiliza múltiples pestañas del navegador o dispositivos.
2.2 Consumidores
El cliente de una conexión WebSocket se llama consumidor. En Action Cable, el consumidor es creado por el framework de JavaScript en el lado del cliente.
2.3 Canales
Cada consumidor puede, a su vez, suscribirse a múltiples canales. Cada canal encapsula una unidad lógica de trabajo, similar a lo que hace un controlador en una configuración típica de MVC. Por ejemplo, podrías tener un ChatChannel
y un AppearancesChannel
, y un consumidor podría estar suscrito a uno o ambos de estos canales. Como mínimo, un consumidor debe estar suscrito a un canal.
2.4 Suscriptores
Cuando el consumidor está suscrito a un canal, actúa como un suscriptor. La conexión entre el suscriptor y el canal se llama, sorpresa sorpresa, una suscripción. Un consumidor puede actuar como suscriptor de un canal dado cualquier número de veces. Por ejemplo, un consumidor podría suscribirse a múltiples salas de chat al mismo tiempo. (Y recuerda que un usuario físico puede tener múltiples consumidores, uno por pestaña/dispositivo abierto en tu conexión).
2.5 Pub/Sub
Pub/Sub o Publicar-Suscribir se refiere a un paradigma de cola de mensajes en el que los remitentes de información (publicadores) envían datos a una clase abstracta de destinatarios (suscriptores), sin especificar destinatarios individuales. Action Cable utiliza este enfoque para comunicarse entre el servidor y muchos clientes.
2.6 Difusión
Una difusión es un enlace pub/sub donde todo lo transmitido por el emisor se envía directamente a los suscriptores del canal que están transmitiendo esa difusión nombrada. Cada canal puede estar transmitiendo cero o más difusiones.
3 Componentes del lado del servidor
3.1 Conexiones
Por cada WebSocket aceptado por el servidor, se instancia un objeto de conexión. Este objeto se convierte en el padre de todas las suscripciones de canal que se crean a partir de ese momento. La conexión en sí no se ocupa de ninguna lógica de aplicación específica más allá de la autenticación y autorización. El cliente de una conexión WebSocket se llama consumidor de la conexión. Un usuario individual creará una pareja consumidor-conexión por cada pestaña del navegador, ventana o dispositivo que tenga abierto.
Las conexiones son instancias de ApplicationCable::Connection
, que extiende ActionCable::Connection::Base
. En ApplicationCable::Connection
, autorizas la conexión entrante y procedes a establecerla si el usuario puede ser identificado.
3.1.1 Configuración de la conexión
# 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
Aquí, identified_by
designa un identificador de conexión que se puede utilizar para encontrar la conexión específica más adelante. Ten en cuenta que cualquier cosa marcada como un identificador creará automáticamente un delegado con el mismo nombre en todas las instancias de canal creadas a partir de la conexión.
Este ejemplo se basa en el hecho de que ya habrás manejado la autenticación del usuario en otro lugar de tu aplicación, y que una autenticación exitosa establece una cookie encriptada con el ID del usuario.
La cookie se envía automáticamente a la instancia de conexión cuando se intenta una nueva conexión, y la utilizas para establecer current_user
. Al identificar la conexión con este mismo usuario actual, también te aseguras de que luego puedas recuperar todas las conexiones abiertas por un usuario dado (y potencialmente desconectarlas todas si el usuario se elimina o no está autorizado).
Si su enfoque de autenticación incluye el uso de una sesión, utiliza el almacenamiento de cookies para la sesión, su cookie de sesión se llama _session
y la clave del ID de usuario es user_id
, puede utilizar este enfoque:
verified_user = User.find_by(id: cookies.encrypted['_session']['user_id'])
3.1.2 Manejo de excepciones
De forma predeterminada, las excepciones no controladas se capturan y se registran en el registro de Rails. Si desea interceptar globalmente estas excepciones y reportarlas a un servicio externo de seguimiento de errores, por ejemplo, puede hacerlo con 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 conexión
Hay callbacks before_command
, after_command
y around_command
disponibles para invocar antes, después o alrededor de cada comando recibido por un cliente respectivamente.
El término "comando" aquí se refiere a cualquier interacción recibida por un cliente (suscribirse, cancelar la suscripción o realizar acciones):
# 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)
# Ahora todos los canales pueden usar Current.account
Current.set(account: user.account, &block)
end
end
end
3.2 Canales
Un canal encapsula una unidad lógica de trabajo, similar a lo que hace un controlador en una configuración típica de MVC. De forma predeterminada, Rails crea una clase padre ApplicationCable::Channel
(que extiende ActionCable::Channel::Base
) para encapsular la lógica compartida entre sus canales.
3.2.1 Configuración del canal padre
# app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
Luego puede crear sus propias clases de canal. Por ejemplo, podría tener un ChatChannel
y un AppearanceChannel
:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end
Un consumidor podría suscribirse a uno o ambos de estos canales.
3.2.2 Suscripciones
Los consumidores se suscriben a canales, actuando como suscriptores. Su conexión se llama una suscripción. Los mensajes producidos se envían a estas suscripciones de canal según un identificador enviado por el consumidor del canal.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Se llama cuando el consumidor se ha convertido con éxito en un suscriptor de este canal.
def subscribed
end
end
3.2.3 Manejo de excepciones
Al igual que con ApplicationCable::Connection
, también puede usar rescue_from
en un
canal específico para manejar excepciones lanzadas:
# 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
proporciona una serie de callbacks que se pueden utilizar para activar la lógica
durante el ciclo de vida de un canal. Los callbacks disponibles son:
before_subscribe
after_subscribe
(también alias:on_subscribe
)before_unsubscribe
after_unsubscribe
(también alias:on_unsubscribe
)
NOTA: El callback after_subscribe
se activa cada vez que se llama al método subscribed
,
incluso si la suscripción fue rechazada con el método reject
. Para activar after_subscribe
solo en suscripciones exitosas, 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 del lado del cliente
4.1 Conexiones
Los consumidores requieren una instancia de la conexión en su lado. Esto se puede establecer utilizando el siguiente JavaScript, que se genera de forma predeterminada en Rails:
4.1.1 Conectar consumidor
// app/javascript/channels/consumer.js
// Action Cable proporciona el marco para trabajar con WebSockets en Rails.
// Puede generar nuevos canales donde se encuentran las características de WebSocket utilizando el comando `bin/rails generate channel`.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
Esto preparará un consumidor que se conectará a /cable
en su servidor de forma predeterminada.
La conexión no se establecerá hasta que también haya especificado al menos una suscripción
que le interese tener.
El consumidor opcionalmente puede tomar un argumento que especifica la URL a la que conectarse. Esto puede ser una cadena o una función que devuelve una cadena que se llamará cuando se abra el WebSocket.
// Especificar una URL diferente a la que conectarse
createConsumer('wss://example.com/cable')
// O cuando se usan websockets sobre HTTP
createConsumer('https://ws.example.com/cable')
// Usar una función para generar dinámicamente la URL
createConsumer(getWebSocketURL)
function getWebSocketURL() {
const token = localStorage.get('auth-token')
return `wss://example.com/cable?token=${token}`
}
4.1.2 Suscriptor
Un consumidor se convierte en un suscriptor creando una suscripción a un canal dado:
// 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" })
Si bien esto crea la suscripción, la funcionalidad necesaria para responder a los datos recibidos se describirá más adelante. Un consumidor puede actuar como suscriptor de un canal determinado cualquier número de veces. Por ejemplo, un consumidor podría suscribirse a múltiples salas de chat al mismo tiempo:
// 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 Interacciones Cliente-Servidor
5.1 Streams
Streams proporcionan el mecanismo mediante el cual los canales enrutan contenido publicado (transmisiones) a sus suscriptores. Por ejemplo, el siguiente código utiliza stream_from
para suscribirse a la transmisión llamada chat_Best Room
cuando el valor del parámetro :room
es "Best Room"
:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
Luego, en otra parte de su aplicación de Rails, puede transmitir a esa sala llamando a broadcast
:
ActionCable.server.broadcast("chat_Best Room", { body: "This Room is Best Room." })
Si tiene una transmisión relacionada con un modelo, entonces el nombre de la transmisión se puede generar a partir del canal y el modelo. Por ejemplo, el siguiente código utiliza stream_for
para suscribirse a una transmisión como posts:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
, donde Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
es el GlobalID del modelo Post.
class PostsChannel < ApplicationCable::Channel
def subscribed
post = Post.find(params[:id])
stream_for post
end
end
Luego puede transmitir a este canal llamando a broadcast_to
:
PostsChannel.broadcast_to(@post, @comment)
5.2 Transmisiones
Una transmisión es un enlace pub/sub donde cualquier cosa transmitida por un editor se enruta directamente a los suscriptores del canal que están transmitiendo esa transmisión con nombre. Cada canal puede transmitir cero o más transmisiones.
Las transmisiones son puramente una cola en línea y dependiente del tiempo. Si un consumidor no está transmitiendo (suscripto a un canal determinado), no recibirá la transmisión si se conecta más tarde.
5.3 Suscripciones
Cuando un consumidor se suscribe a un canal, actúa como un suscriptor. Esta conexión se llama suscripción. Los mensajes entrantes se enrutan a estas suscripciones de canal en función de un identificador enviado por el consumidor de cable.
// 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 Pasar parámetros a los canales
Puede pasar parámetros desde el lado del cliente al lado del servidor al crear una suscripción. Por ejemplo:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
Un objeto pasado como primer argumento a subscriptions.create
se convierte en el hash de parámetros en el canal de cable. La palabra clave channel
es requerida:
// 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>
`
}
})
# En algún lugar de su aplicación esto se llama, tal vez
# desde un NewCommentJob.
ActionCable.server.broadcast(
"chat_#{room}",
{
sent_by: 'Paul',
body: 'This is a cool chat app.'
}
)
5.5 Rebroadcast de un mensaje
Un caso de uso común es rebroadcastear un mensaje enviado por un cliente a cualquier otro 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: "This is a cool chat app." }
}
}
chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
El rebroadcast será recibido por todos los clientes conectados, incluyendo el cliente que envió el mensaje. Tenga en cuenta que los parámetros son los mismos que cuando se suscribió al canal.
6 Ejemplos de pila completa
Los siguientes pasos de configuración son comunes a ambos ejemplos:
6.1 Ejemplo 1: Apariciones de usuarios
Aquí hay un ejemplo simple de un canal que rastrea si un usuario está en línea o no y en qué página se encuentra. (Esto es útil para crear características de presencia como mostrar un punto verde junto a un nombre de usuario si está en línea).
Cree el canal de apariciones del lado del 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
Cuando se inicia una suscripción, se activa el callback subscribed
y aprovechamos esa oportunidad para decir "el usuario actual ha aparecido". Esa API de aparecer/desaparecer podría estar respaldada por Redis, una base de datos o cualquier otra cosa.
Crear la suscripción del canal de apariencia del lado del cliente:
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("AppearanceChannel", {
// Llamado una vez cuando se crea la suscripción.
initialized() {
this.update = this.update.bind(this)
},
// Llamado cuando la suscripción está lista para usar en el servidor.
connected() {
this.install()
this.update()
},
// Llamado cuando la conexión WebSocket se cierra.
disconnected() {
this.uninstall()
},
// Llamado cuando la suscripción es rechazada por el servidor.
rejected() {
this.uninstall()
},
update() {
this.documentIsActive ? this.appear() : this.away()
},
appear() {
// Llama a `AppearanceChannel#appear(data)` en el servidor.
this.perform("appear", { appearing_on: this.appearingOn })
},
away() {
// Llama a `AppearanceChannel#away` en el 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 Interacción Cliente-Servidor
Cliente se conecta al Servidor a través de
createConsumer()
. (consumer.js
). El Servidor identifica esta conexión porcurrent_user
.Cliente se suscribe al canal de apariencia a través de
consumer.subscriptions.create({ channel: "AppearanceChannel" })
. (appearance_channel.js
)Servidor reconoce que se ha iniciado una nueva suscripción para el canal de apariencia y ejecuta su callback
subscribed
, llamando al métodoappear
encurrent_user
. (appearance_channel.rb
)Cliente reconoce que se ha establecido una suscripción y llama a
connected
(appearance_channel.js
), que a su vez llama ainstall
yappear
.appear
llama aAppearanceChannel#appear(data)
en el servidor, y suministra un hash de datos de{ appearing_on: this.appearingOn }
. Esto es posible porque la instancia del canal del lado del servidor expone automáticamente todos los métodos públicos declarados en la clase (excepto los callbacks), de modo que estos pueden ser alcanzados como llamadas de procedimiento remoto a través del métodoperform
de una suscripción.Servidor recibe la solicitud de la acción
appear
en el canal de apariencia para la conexión identificada porcurrent_user
(appearance_channel.rb
). Servidor recupera los datos con la clave:appearing_on
del hash de datos y lo establece como el valor para la clave:on
que se pasa acurrent_user.appear
.
6.2 Ejemplo 2: Recibir nuevas notificaciones web
El ejemplo de apariencia se trataba de exponer la funcionalidad del servidor a invocación del lado del cliente a través de la conexión WebSocket. Pero lo genial de los WebSockets es que es una calle de doble sentido. Así que ahora, vamos a mostrar un ejemplo donde el servidor invoca una acción en el cliente.
Este es un canal de notificaciones web que te permite activar notificaciones web del lado del cliente cuando se transmiten a los flujos relevantes:
Crear el canal de notificaciones web del lado del servidor:
# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
end
Crear la suscripción del canal de notificaciones web del lado del cliente:
// app/javascript/channels/web_notifications_channel.js
// Lado del cliente que asume que ya has solicitado
// el permiso para enviar notificaciones web.
import consumer from "./consumer"
consumer.subscriptions.create("WebNotificationsChannel", {
received(data) {
new Notification(data["title"], { body: data["body"] })
}
})
Transmitir contenido a una instancia del canal de notificaciones web desde otro lugar de tu aplicación:
# En algún lugar de tu aplicación esto se llama, tal vez desde un NewCommentJob
WebNotificationsChannel.broadcast_to(
current_user,
title: '¡Cosas nuevas!',
body: 'Todas las noticias dignas de imprimir'
)
La llamada WebNotificationsChannel.broadcast_to
coloca un mensaje en la cola de pubsub del adaptador de suscripción actual bajo un nombre de transmisión separado para cada
usuario. Para un usuario con un ID de 1, el nombre de transmisión sería
web_notifications:1
.
Se ha instruido al canal para que transmita todo lo que llegue a
web_notifications:1
directamente al cliente invocando el callback received
. Los datos pasados como argumento son el hash enviado como segundo parámetro
a la llamada de transmisión del lado del servidor, codificado en JSON para el viaje a través del cable
y desempaquetado para el argumento de datos que llega como received
.
6.3 Ejemplos más completos
Consulta el repositorio rails/actioncable-examples para ver un ejemplo completo de cómo configurar Action Cable en una aplicación Rails y agregar canales.
7 Configuración
Action Cable tiene dos configuraciones requeridas: un adaptador de suscripción y los orígenes de solicitud permitidos.
7.1 Adaptador de suscripción
De forma predeterminada, Action Cable busca un archivo de configuración en config/cable.yml
.
El archivo debe especificar un adaptador para cada entorno de Rails. Consulta la sección
Dependencias para obtener información adicional sobre los adaptadores.
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: redis://10.10.3.153:6381
channel_prefix: appname_production
7.1.1 Configuración del adaptador
A continuación se muestra una lista de los adaptadores de suscripción disponibles para los usuarios finales.
7.1.1.1 Adaptador Async
El adaptador async está destinado para desarrollo/pruebas y no debe utilizarse en producción.
7.1.1.2 Adaptador Redis
El adaptador Redis requiere que los usuarios proporcionen una URL que apunte al servidor Redis.
Además, se puede proporcionar un channel_prefix
para evitar colisiones de nombres de canal
cuando se utiliza el mismo servidor Redis para varias aplicaciones. Consulte la documentación de Redis Pub/Sub para obtener más detalles.
El adaptador Redis también admite conexiones SSL/TLS. Los parámetros SSL/TLS requeridos se pueden pasar en la clave ssl_params
en el archivo de configuración YAML.
production:
adapter: redis
url: rediss://10.10.3.153:tls_port
channel_prefix: appname_production
ssl_params: {
ca_file: "/path/to/ca.crt"
}
Las opciones dadas a ssl_params
se pasan directamente al método OpenSSL::SSL::SSLContext#set_params
y pueden ser cualquier atributo válido del contexto SSL.
Consulte la documentación de OpenSSL::SSL::SSLContext para obtener otros atributos disponibles.
Si está utilizando certificados autofirmados para el adaptador Redis detrás de un firewall y opta por omitir la verificación del certificado, entonces el verify_mode
SSL debe establecerse como OpenSSL::SSL::VERIFY_NONE
.
ADVERTENCIA: No se recomienda utilizar VERIFY_NONE
en producción a menos que comprenda absolutamente las implicaciones de seguridad. Para establecer esta opción para el adaptador Redis, la configuración debe ser ssl_params: { verify_mode: <%= OpenSSL::SSL::VERIFY_NONE %> }
.
7.1.1.3 Adaptador PostgreSQL
El adaptador PostgreSQL utiliza el grupo de conexiones de Active Record y, por lo tanto, la
configuración de la base de datos en config/database.yml
de la aplicación para su conexión.
Esto puede cambiar en el futuro. #27214
7.2 Orígenes de solicitud permitidos
Action Cable solo aceptará solicitudes de los orígenes especificados, que se pasan a la configuración del servidor como un array. Los orígenes pueden ser instancias de cadenas o expresiones regulares, contra las cuales se realizará una comprobación de coincidencia.
config.action_cable.allowed_request_origins = ['https://rubyonrails.com', %r{http://ruby.*}]
Para deshabilitar y permitir solicitudes desde cualquier origen:
config.action_cable.disable_request_forgery_protection = true
De forma predeterminada, Action Cable permite todas las solicitudes desde localhost:3000 cuando se ejecuta en el entorno de desarrollo.
7.3 Configuración del consumidor
Para configurar la URL, agregue una llamada a action_cable_meta_tag
en su diseño HTML
HEAD. Esto utiliza una URL o ruta que generalmente se establece a través de config.action_cable.url
en los
archivos de configuración del entorno.
7.4 Configuración del grupo de trabajadores
El grupo de trabajadores se utiliza para ejecutar devoluciones de llamada de conexión y acciones de canal de forma aislada del hilo principal del servidor. Action Cable permite que la aplicación configure la cantidad de hilos procesados simultáneamente en el grupo de trabajadores.
config.action_cable.worker_pool_size = 4
Además, tenga en cuenta que su servidor debe proporcionar al menos la misma cantidad de conexiones de base de datos
que la cantidad de trabajadores que tiene. El tamaño predeterminado del grupo de trabajadores se establece en 4, por lo que
eso significa que debe tener al menos 4 conexiones de base de datos disponibles.
Puede cambiar eso en config/database.yml
a través del atributo pool
.
7.5 Registro del lado del cliente
El registro del lado del cliente está desactivado de forma predeterminada. Puede habilitarlo configurando ActionCable.logger.enabled
como verdadero.
import * as ActionCable from '@rails/actioncable'
ActionCable.logger.enabled = true
7.6 Otras configuraciones
La otra opción común para configurar son las etiquetas de registro aplicadas al registro de conexión por conexión. Aquí hay un ejemplo que utiliza el ID de cuenta de usuario si está disponible, de lo contrario "no-account" mientras etiqueta:
config.action_cable.log_tags = [
-> request { request.env['user_account_id'] || "no-account" },
:action_cable,
-> request { request.uuid }
]
Para obtener una lista completa de todas las opciones de configuración, consulte la
clase ActionCable::Server::Configuration
.
8 Ejecución de servidores de Cable independientes
Action Cable puede ejecutarse como parte de su aplicación Rails o como un servidor independiente. En desarrollo, ejecutarlo como parte de su aplicación Rails generalmente está bien, pero en producción debe ejecutarlo como un servidor independiente.
8.1 En la aplicación
Action Cable puede ejecutarse junto con su aplicación Rails. Por ejemplo, para
escuchar solicitudes de WebSocket en /websocket
, especifique esa ruta en
config.action_cable.mount_path
:
# config/application.rb
class Application < Rails::Application
config.action_cable.mount_path = '/websocket'
end
Puede usar ActionCable.createConsumer()
para conectarse al servidor de cable
si se invoca action_cable_meta_tag
en el diseño. De lo contrario, se especifica una ruta como
primer argumento para createConsumer
(por ejemplo, ActionCable.createConsumer("/websocket")
).
Por cada instancia de su servidor que cree y por cada trabajador que su servidor genere, también tendrá una nueva instancia de Action Cable, pero el adaptador Redis o PostgreSQL mantiene los mensajes sincronizados entre las conexiones.
8.2 Independiente
Los servidores de cable se pueden separar de su servidor de aplicaciones normal. Es aún una aplicación Rack, pero es su propia aplicación Rack. La configuración básica recomendada es la siguiente: ```ruby
cable/config.ru
require_relative "../config/environment" Rails.application.eager_load!
run ActionCable.server ```
Luego, para iniciar el servidor:
bundle exec puma -p 28080 cable/config.ru
Esto inicia un servidor de cable en el puerto 28080. Para indicarle a Rails que use este servidor, actualiza tu configuración:
# config/environments/development.rb
Rails.application.configure do
config.action_cable.mount_path = nil
config.action_cable.url = "ws://localhost:28080" # usar wss:// en producción
end
Finalmente, asegúrate de haber configurado correctamente el consumidor.
8.3 Notas
El servidor WebSocket no tiene acceso a la sesión, pero sí tiene acceso a las cookies. Esto se puede utilizar cuando se necesita manejar la autenticación. Puedes ver una forma de hacerlo con Devise en este artículo.
9 Dependencias
Action Cable proporciona una interfaz de adaptador de suscripción para procesar su
interno de pubsub. Por defecto, se incluyen adaptadores asíncronos, en línea, PostgreSQL y Redis.
El adaptador predeterminado
en nuevas aplicaciones de Rails es el adaptador asíncrono (async
).
El lado de Ruby se basa en websocket-driver, nio4r y concurrent-ruby.
10 Implementación
Action Cable funciona con una combinación de WebSockets y hilos. Tanto la infraestructura del framework como el trabajo de canal especificado por el usuario se manejan internamente utilizando el soporte nativo de hilos de Ruby. Esto significa que puedes usar todos tus modelos de Rails existentes sin problemas, siempre y cuando no hayas cometido ningún pecado de seguridad de hilos.
El servidor de Action Cable implementa la API de secuestro de socket de Rack, lo que permite el uso de un patrón multihilo para gestionar las conexiones internamente, independientemente de si el servidor de la aplicación es multihilo o no.
En consecuencia, Action Cable funciona con servidores populares como Unicorn, Puma y Passenger.
11 Pruebas
Puedes encontrar instrucciones detalladas sobre cómo probar la funcionalidad de Action Cable en la guía de pruebas.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.