Implementación del servidor de mensajería de habitación que utiliza un protocolo RPC bidireccional para implementar la comunicación similar a la de chat. Diseñado para manejar problemas comunes de mensajería de redes públicas como entrega confiable, múltiples conexiones de un solo usuario, permisos en tiempo real y presencia. El procesamiento de solicitudes de RPC y el formato de mensajes de habitación son personalizables a través de ganchos, lo que permite implementar cualquier cosa, desde un servidor de salas de chat hasta una aplicación de colaboración con una resolución de conflictos compleja. Los mensajes de la sala también se pueden usar para crear API públicas o para túnel M2M Communications para dispositivos IoT.
Mensajes de habitación confiables utilizando un almacenamiento del historial del lado del servidor y una API de sincronización.
Formato de mensajes arbitrarios a través de solo una función de validación (gancho), permitiendo formatos de mensajes personalizados/heterogéneos (incluidos los datos binarios dentro de los mensajes).
API de presencia del usuario por habitación con notificaciones.
Creación de habitaciones en tiempo real y API de gestión de permisos por habitación por habitación. Soporte para modos de acceso basados en la lista negra o de la lista blanca y un grupo de administradores opcionales.
Soporte sin interrupciones de las conexiones de múltiples usuarios desde varios dispositivos a cualquier instancia de servicio.
Escrito como un microservicio sin estado, utiliza Redis (también admite configuraciones de clúster) como una tienda de estado, se puede escalar horizontalmente a pedido.
Extenso soporte de personalización. La funcionalidad personalizada se puede agregar a través de ganchos antes/después para cualquier procesamiento de solicitud de cliente. Y los manejadores de solicitudes (comandos) se pueden invocar el lado del servidor a través de una API.
Transporte de red pluginable. La comunicación cliente-servidor se realiza a través de un protocolo RPC bidireccional. Se incluye la implementación de transporte de Socket.io.
Tienda de estado pluginable. Se incluyen las tiendas de memoria y redis.
Admite el usuario en línea liviano para la mensajería de usuarios en línea.
Lea este artículo para obtener más información sobre antecedentes.
Este proyecto es un módulo de nodo disponible a través de NPM. Ve a verlos si no los tienes instalados localmente.
$ npm i chat-servicePrimero defina la configuración de un servidor. En un lado del servidor, defina un gancho de conexión de socket, ya que el servicio se basa en una implementación de autores externos. Un usuario solo necesita pasar una verificación de autenticación, no se requiere un paso explícito que agregue paso.
const ChatService = require ( 'chat-service' )
const port = 8000
function onConnect ( service , id ) {
// Assuming that auth data is passed in a query string.
let { query } = service . transport . getHandshakeData ( id )
let { userName } = query
// Actually check auth data.
// ...
// Return a promise that resolves with a login string.
return Promise . resolve ( userName )
} Crear un servidor es una simple instanciación de objetos. Nota: Se debe llamar al método close para cerrar correctamente una instancia de servicio (consulte la recuperación de fallas).
const chatService = new ChatService ( { port } , { onConnect } )
process . on ( 'SIGINT' , ( ) => chatService . close ( ) . finally ( ( ) => process . exit ( ) ) ) El servidor ahora se ejecuta en el puerto 8000 , utilizando el estado memory . De forma predeterminada, se utiliza el espacio de nombres de Socket.io '/chat-service' . Agregue una habitación con el usuario admin como propietario de la habitación. Todas las habitaciones deben ser creadas explícitamente (también se proporciona la opción para permitir la creación de habitaciones desde un lado del cliente).
// The room configuration and messages will persist if redis state is
// used. addRoom will reject a promise if the room is already created.
chatService . hasRoom ( 'default' ) . then ( hasRoom => {
if ( ! hasRoom ) {
return chatService . addRoom ( 'default' , { owner : 'admin' } )
}
} ) En un cliente solo se requiere una implementación socket.io-client . Para enviar una solicitud (comando) usar el método emit , el resultado (o un error) se devolverá en la devolución de llamada Socket.io ACK. Para escuchar los mensajes del servidor usar on el método.
const io = require ( 'socket.io-client' )
// Use https or wss in production.
let url = 'ws://localhost:8000/chat-service'
let userName = 'user' // for example and debug
let token = 'token' // auth token
let query = `userName= ${ userName } &token= ${ token } `
let opts = { query }
// Connect to a server.
let socket = io . connect ( url , opts )
// Rooms messages handler (own messages are here too).
socket . on ( 'roomMessage' , ( room , msg ) => {
console . log ( ` ${ msg . author } : ${ msg . textMessage } ` )
} )
// Auth success handler.
socket . on ( 'loginConfirmed' , userName => {
// Join room named 'default'.
socket . emit ( 'roomJoin' , 'default' , ( error , data ) => {
// Check for a command error.
if ( error ) { return }
// Now we will receive 'default' room messages in 'roomMessage' handler.
// Now we can also send a message to 'default' room:
socket . emit ( 'roomMessage' , 'default' , { textMessage : 'Hello!' } )
} )
} )
// Auth error handler.
socket . on ( 'loginRejected' , error => {
console . error ( error )
} ) Es un código ejecutable, los archivos están en el directorio example .
Es posible usar otros transportes que no sean Socket.io. Existe una prueba de transporte conceptual, que está utilizando una conexión WebSocket con una capa mínima de abstracción API WS-Messaging y un simple emisor-Pubsub-Broker como abstracción de faneut de mensajes de backend.
Aquí están las cosas principales que un transporte debe permitir:
Envíe mensajes de un servidor a grupos de clientes (basados en una única cadena de criterios de coincidencia, también conocido como mensajería de sala).
Implemente la comunicación de solicitud de solicitud de un cliente a un servidor.
Implementar algún tipo de conexión persistente (o semánticamente equivalente), es necesario para un seguimiento de presencia.
El servicio de chat está utilizando Redis como una tienda compartida con persistencia. En una aplicación real, otros servicios pueden necesitar parte de esta información, pero no es práctico volver a implementar completamente la tienda estatal. Un mejor enfoque alternativo es usar ganchos. Por ejemplo, para guardar todos los mensajes de la habitación dentro de otra base de datos, solo se puede usar un gancho roomMessageAfter . También ServiceAPI puede expuestos a través de autobuses de mensajería de backend a otros servidores internos.
En circunstancias normales, todos los errores que se devuelven a un usuario del servicio (a través de respuestas de solicitud, Mensajes loginConfirmed o loginRejected ) son instancias de ChatServiceError . Todos los demás errores indican un error del programa o una falla en una infraestructura de servicio. Para habilitar el registro de depuración de tales errores, use export NODE_DEBUG=ChatService . La biblioteca está utilizando Bluebird ^3.0.0 promete la implementación, por lo que para habilitar las trazas de pila largas use export BLUEBIRD_DEBUG=1 . Se recomienda altamente utilizar versiones prometedoras de API para ganchos y subclases ChatServiceError para devolver los errores personalizados.
La API del lado del servidor y la documentación RPC están disponibles en línea.
El servicio abstrae completamente un concepto de conexión de un concepto de usuario, por lo que un solo usuario puede tener más de una conexión (incluidas las conexiones en diferentes nodos). Para la presencia del usuario, el número de enchufes unidos debe ser poco mayor que cero. Todas las API diseñadas para funcionar a nivel de usuario, manejando sin problemas las conexiones múltiples del usuario.
Las conexiones son completamente independientes, no se requiere soporte adicional del lado del cliente. Pero hay mensajes de información y comandos que se pueden usar para obtener información sobre las conexiones de otros usuarios. Hace posible realizar patrones de sincronización del lado del cliente, como mantener todas las conexiones para unirse a las mismas habitaciones.
Cada habitación tiene un sistema de permisos. Hay un solo usuario propietario, que tiene todos los privilegios de administrador y puede asignar usuarios al grupo de administradores. Los administradores pueden administrar los permisos de acceso de otros usuarios. Se admiten dos modos: la lista negra y la lista blanca. Después de las listas de acceso/modificaciones del modo, el servicio elimina automáticamente a los usuarios que han perdido un permiso de acceso.
Si las opciones de enableRoomsManagement están habilitados, los usuarios pueden crear habitaciones a través del comando roomCreate . El creador de una habitación será su dueño y también puede eliminarlo a través del comando roomDelete .
Antes de que los ganchos puedan usarse para implementar sistemas de permisos adicionales.
Cuando un usuario envía un mensaje de habitación, en RPC respuesta se devuelve el id de mensaje. Significa que el mensaje se ha guardado en una tienda (en una estructura de buffer circular solo). Las ID de mensaje de la habitación son una secuencia que comienza desde 1 , que aumenta en una para cada mensaje enviado con éxito en la habitación. Un cliente siempre puede verificar la ID de mensaje de la última habitación a través del comando roomHistoryInfo y usar el comando roomHistoryGet para obtener mensajes faltantes. Tal enfoque asegura que se pueda recibir un mensaje, a menos que se elimine debido a la rotación.
Por defecto, un cliente puede enviar mensajes que se limitan a solo un {textMessage: 'Some string'} . Para habilitar el formato de mensajes personalizados, proporcione directMessagesChecker o roomMessagesChecker ganchos. Cuando se resuelve un gancho, se acepta un formato de mensaje. Los mensajes pueden ser datos arbitrarios con algunas restricciones. El nivel superior debe ser un Object , sin timestamp , author o campos id (el servicio llenará estos campos antes de enviar mensajes). Los niveles anidados pueden incluir tipos de datos arbitrarios (incluso binarios), pero no hay objetos anidados con un type campo establecido en 'Buffer' (utilizado para manipulaciones de datos binarios).
Cada comando de usuario admite antes y después de la adición de gancho, y también se admiten una conexión del cliente/ganchos de desconexión. Los comandos y los ganchos se ejecutan secuencialmente: antes del gancho - comando - después del gancho (también se llamará a los errores de comando). La terminación de la secuencia en antes de los ganchos es posible. Los clientes pueden enviar argumentos de comando adicionales, los ganchos pueden leerlos y responder con argumentos adicionales.
Para ejecutar un lado del servidor de comandos de usuario, se proporciona execUserCommand . También hay algunos métodos más del lado del servidor proporcionados por ServiceAPI y TransportInterface . Busque algunos casos de personalización en ejemplos de personalización.
El servicio mantiene los datos de presencia y conexión del usuario en una tienda, que pueden ser persistentes o compartidos. Entonces, si una instancia se apaga incorrectamente (sin llamar o esperar a que finalice el método de close ) o se pierda la conexión completamente de red a un almacén, los datos de presencia se volverán incorrectos. Para corregir este caso, se proporciona un método instanceRecovery .
También hay casos más sutiles con respecto a la consistencia de datos dependientes de la conexión. Las instancias de comunicación de transporte y las instancias de la tienda pueden experimentar varios tipos de fallas en la red, el software o el hardware. En algunos casos de borde (como la operación en múltiples usuarios), tales fallas pueden causar inconsistencias (para la mayor parte, los errores se devolverán a los emisores del comando). Estos eventos se informan a través de un emisor de instancias (como el evento storeConsistencyFailure ), y los datos pueden sincronizar a través de los métodos RecoveryAPI .
Por defecto, se supone que cada usuario tiene un inicio de sesión único (nombre de usuario). En lugar de administrar la generación de nombres, se puede usar una integración con un transporte separado (o una conexión multiplexada, por ejemplo, otro espacio de nombres Socket.io). Los mensajes de la habitación se pueden reenviar desde roomMessage tras gancho a un transporte, a quienes se puede acceder sin inicio de sesión. Y viceversa Algunos comandos de servicio pueden ser ejecutados por usuarios anónimos a través de execUserCommand con la opción de permisos de omisión activada.
Un roomMessage tras gancho también se puede usar para reenviar mensajes de una habitación a otra. Entonces, las habitaciones se pueden usar para la agregación de mensajes desde otras habitaciones. Dado que los ganchos son solo funciones y tienen un acceso completo al contenido de mensajes, permite implementar reglas de reenvío arbitrarias basadas en contenido. Incluida la implementación de sistemas con alimentos específicos de usuario altamente personalizados (cliente).
Por defecto, no hay forma de que otros usuarios conozcan el número y los tipos de conexiones de usuario unidas a una habitación. Dicha información se puede aprobar, por ejemplo, en una cadena de consulta y luego guardada a través de un gancho de conexión. El anuncio se puede hacer en los ganchos onJoin y onLeave , utilizando el método sendToChannel de transporte directo. También se debe enviar información adicional sobre los tipos de dispositivos unidos desde roomGetAccessList después del gancho (cuando el nombre de la lista es igual a 'userlist' ).
No hay operación de eliminación o edición, ya que harán inconsistencias dentro del historial de una habitación. Una alternativa común para eliminar y editar es usar mensajes de habitación con un significado especial que los clientes usarán para ocultar o alterar mensajes.
Si encuentra un error en este paquete, envíe un informe de error a los problemas de repositorio de GitHub.
Los PR también son aceptados.
MIT