使用雙向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問題提交錯誤報告。
公關也被接受。
麻省理工學院