使用双向RPC协议实现类似聊天的通信的房间消息传递服务器实现。旨在处理常见的公共网络消息传递问题,例如可靠交付,单个用户的多个连接,实时权限和存在。 RPC请求处理和房间消息格式可通过挂钩自定义,从而可以实现从Chat-rooms Server到具有复杂冲突解决方案的协作应用程序的任何内容。房间消息也可用于创建公共API或用于物联网设备的M2M通信。
使用服务器端历史存储和同步API的可靠房间消息传递。
仅通过验证函数(Hook)进行任意消息格式,允许自定义/异质消息格式(包括消息内部的二进制数据)。
带通知的每室用户存在API。
实时房间创建和每间用户权限管理API。支持基于黑名单或白名单的访问模式和可选管理员组的支持。
从各种设计到任何服务实例的多个用户连接的无缝支持。
可以将Redis(也支持集群配置)作为状态存储编写为无状态的微服务,可以按需水平缩放。
广泛的自定义支持。可以在任何客户端请求处理之前/之后通过挂钩添加自定义功能。并且请求(命令)处理程序可以通过API调用服务器端。
可插入的网络传输。客户服务器通信是通过双向RPC协议完成的。包括socket.io运输实施。
可插入的状态商店。包括内存和REDIS商店。
支持轻巧的在线用户到在线用户消息传递。
阅读本文以获取更多背景信息。
该项目是通过NPM提供的节点模块。如果您没有本地安装,请检查一下。
$ npm i chat-service首先定义服务器配置。在服务器端定义套接字连接挂钩,因为该服务依赖于外部AUTH实现。用户只需要通过验证检查,不需要明确的用户添加步骤。
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 )
}创建服务器是一个简单的对象实例化。注意:必须调用close方法以正确关闭服务实例(请参阅Failures恢复)。
const chatService = new ChatService ( { port } , { onConnect } )
process . on ( 'SIGINT' , ( ) => chatService . close ( ) . finally ( ( ) => process . exit ( ) ) )服务器现在使用memory状态在端口8000上运行。默认情况下使用'/chat-service'命名空间。与admin用户一起添加一个房间作为房间所有者。所有房间都必须明确创建(还提供了从客户端创建房间的选项)。
// 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' } )
}
} )在客户端上,只需要一个socket.io-client实现。要发送请求(命令)使用emit方法,结果(或错误)将在socket.io ack回调中返回。收听在方法on使用的服务器消息。
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 )
} )这是一个可运行的代码,文件在example目录中。
除socket.io以外,还可以使用其他运输。有一个概念传输的证明,它使用Websocket连接使用一些最小的API抽象层WS-MESSGAGE和一个简单的Emitter-PubSub-Broker作为后端消息传递粉丝抽象。
以下是运输必须允许的主要事情:
将消息从服务器发送到一组客户端(基于单个字符串完整匹配条件,又称室消息传递)。
实现从客户端到服务器的请求 - 重新通信。
实现某种持续的连接(或语义上等效),这是在线跟踪所必需的。
聊天服务将Redis用作具有持久性的共享商店。在实际应用中,其他服务可能需要一些此类信息,但是完全重新实现州商店是不切实际的。一种更好的替代方法是使用钩子。例如,为了在另一个数据库中保存所有房间消息,只需使用roomMessageAfter挂钩即可。另外, ServiceAPI可以通过后端消息总线暴露于其他内部服务器。
在正常情况下,返回服务用户的所有错误(通过请求答复, loginConfirmed或loginRejected消息)都是ChatServiceError的实例。所有其他错误表示服务基础架构中的程序错误或故障。要启用此类错误的调试日志记录,请使用export NODE_DEBUG=ChatService 。该库使用的是Bluebird ^3.0.0承诺实现,因此要启用长堆栈跟踪,请使用export BLUEBIRD_DEBUG=1 。强烈建议将API的Promise版本用于挂钩和ChatServiceError子类以返回挂钩自定义错误。
服务器端API和RPC文档可在线获得。
服务完全从用户概念中抽象一个连接概念,因此单个用户可以具有多个连接(包括跨不同节点的连接)。对于用户的存在,连接插座的数量必须大于零。所有旨在在用户级别上工作的API,可以无缝处理用户的多个连接。
连接是完全独立的,无需其他客户侧支持。但是,有一些信息消息和命令可用于获取有关其他用户连接的信息。可以实现客户端同步模式,例如将所有连接连接到同一房间。
每个房间都有一个权限系统。有一个单个所有者用户,它具有所有管理员特权,并且可以将用户分配给管理员组。管理员可以管理其他用户的访问权限。支持两种模式:黑名单和白名单。访问列表/模式修改后,服务将自动删除丢失访问权限的用户。
如果启用enableRoomsManagement选项,则可以通过roomCreate命令创建房间。房间的创建者将是其所有者,也可以通过roomDelete命令将其删除。
在挂钩可以用于实现其他权限系统之前。
当用户发送房间消息时,在RPC中回复返回消息id 。这意味着该消息已保存在商店中(仅在附加的圆形缓冲区(例如结构)中)。房间消息ID是从1开始的序列,每个成功发送的消息都增加了一个。客户端始终可以通过roomHistoryInfo命令检查最后一个房间消息ID,并使用roomHistoryGet命令获取丢失的消息。这种方法可确保可以收到消息,除非由于旋转而被删除。
默认情况下,客户端可以发送仅限于{textMessage: 'Some string'}消息。为了启用自定义消息格式提供directMessagesChecker或roomMessagesChecker Hooks。当钩子解析时,接受消息格式。消息可以是有几个限制的任意数据。顶级必须是一个Object ,而没有timestamp , author或id字段(在发送消息之前,服务将填写此字段)。嵌套级别可以包括任意数据类型(甚至二进制),但没有将字段type设置为'Buffer' (用于二进制数据操作)的嵌套对象。
每个用户命令都支持挂钩之前和之后,并且也支持客户端连接/断开连接挂钩。命令和钩子被顺序执行:挂钩之前 - 挂钩之后(也将在命令错误中调用)。挂钩之前的序列终止是可能的。客户可以发送其他命令参数,钩子可以阅读它们,并使用其他参数回复。
为了执行用户命令服务器端execUserCommand 。另外, ServiceAPI和TransportInterface提供的仅提供了更多服务器端的方法。在自定义示例中查找一些自定义案例。
服务可以将用户的存在和连接数据保留在商店中,这可能是持久或共享的。因此,如果一个实例被错误地关闭(无需打电话或等待close方法完成)或丢失了与商店的完全网络连接,则存在数据将变得不正确。为了解决此情况,提供了instanceRecovery方法。
同样,关于连接依赖的数据一致性还有更多细微的案例。运输通信实例和商店实例可以体验各种网络,软件或硬件故障。在某些边缘情况下(例如在多个用户上操作),此类故障可能会导致不一致(在大多数情况下,错误将返回命令的发行人)。这些事件是通过实例发射极(例如storeConsistencyFailure事件)报告的,并且可以通过RecoveryAPI方法同步数据。
默认情况下,假定每个用户具有唯一的登录(用户名)。可以使用具有单独传输的集成(或多路复用连接,例如另一个socket.io名称空间),而不是管理名称生成。房间消息可以从挂钩后的roomMessage转发到运输,而无需登录就可以访问。反之亦然,匿名用户可以通过execUserCommand执行某些服务命令,并绕开权限选项打开。
挂钩之后的roomMessage木材也可用于将消息从一个房间转发到另一个房间。因此房间可用于从另一个房间聚集的消息。由于挂钩只是功能,并且可以完全访问消息内容,因此它允许实现基于任意内容的转发规则。包括使用高度个性化的用户(客户端)特定提要实施系统。
默认情况下,其他用户无法知道连接到房间的用户连接的数量和类型。可以通过查询字符串传递此类信息,然后通过连接钩保存。可以使用直接传输sendToChannel方法在onJoin和onLeave钩子上进行公告。此外,有关加入设备类型的其他信息应在挂钩之后从roomGetAccessList发送(当列表名称等于'userlist'时)。
没有删除或编辑操作,因为它们会在房间历史上造成不一致之处。删除和编辑的一种常见替代方法是使用具有特殊含义的房间消息,客户将用来隐藏或更改消息。
如果您在此软件包中遇到错误,请向GitHub Repo问题提交错误报告。
公关也被接受。
麻省理工学院