双方向のRPCプロトコルを使用してチャットのような通信を実装しているルームメッセージングサーバーの実装。信頼できる配信、単一のユーザーからの複数の接続、リアルタイムのアクセス許可、存在など、一般的なパブリックネットワークメッセージングの問題を処理するように設計されています。 RPCリクエストの処理とルームメッセージ形式はフック経由でカスタマイズ可能であり、チャットルームサーバーから複雑な競合解決との共同アプリケーションまで何でも実装できます。部屋のメッセージは、パブリックAPIを作成したり、IoTデバイス用のM2M通信をトンネルしたりするためにも使用できます。
サーバー側の履歴ストレージと同期APIを使用した信頼できるルームメッセージ。
検証関数(フック)のみを介して任意のメッセージ形式を使用して、カスタム/異種メッセージフォーマット(メッセージ内のバイナリデータを含む)を許可します。
通知を備えた部屋ごとのユーザープレゼンスAPI。
リアルタイムルームの作成と部屋ごとのユーザー許可管理API。ブラックリストまたはホワイトリストベースのアクセスモードとオプションの管理者グループのサポート。
さまざまな開発者からあらゆるサービスインスタンスへの複数のユーザーの接続のシームレスなサポート。
ステートレスマイクロサービスとして記述されたレディス(クラスター構成もサポート)をステートストアとして使用し、オンデマンドで水平方向にスケーリングできます。
広範なカスタマイズサポート。カスタム機能は、クライアントリクエスト処理の前後にフックを介して追加できます。リクエスト(コマンド)ハンドラーは、APIを介してサーバー側を呼び出すことができます。
プラグイン可能なネットワーキングトランスポート。クライアントサーバー通信は、双方向RPCプロトコルを介して行われます。 socket.ioトランスポートの実装が含まれています。
プラグイン可能なステートストア。メモリとRedisストアが含まれています。
軽量のオンラインユーザーからオンラインユーザーメッセージをサポートします。
その他の背景情報については、この記事を読んでください。
このプロジェクトは、NPMから利用可能なノードモジュールです。地元でインストールされていない場合は、チェックしてください。
$ npm i chat-service最初にサーバー構成を定義します。サーバー側では、サービスがextern 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メソッドを呼び出す必要があります(障害回復を参照)。
const chatService = new ChatService ( { port } , { onConnect } )
process . on ( 'SIGINT' , ( ) => chatService . close ( ) . finally ( ( ) => process . exit ( ) ) )サーバーは、 memory状態を使用してポート8000で実行されています。デフォルトでは'/chat-service' socket.ioネームスペースが使用されます。 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以外の他のトランスポートを使用することができます。概念の実証輸送があります。これは、いくつかの最小限のAPI抽象化レイヤーWSメッセージと、バックエンドメッセージングファンアウトアブストラクションとしてのシンプルなエミッターパブサブブローカーを使用してWebSocket接続を使用しています。
輸送が許可しなければならない主なことは次のとおりです。
サーバーからクライアントのグループにメッセージを送信します(単一の文字列フルマッチ基準、別名ルームメッセージングに基づいて)。
クライアントからサーバーへのリクエスト対応通信を実装します。
ある種の永続的な接続(または意味的に同等)を実装すると、存在追跡に必要です。
チャットサービスは、Redisを永続的な共有ストアとして使用しています。実際のアプリケーションでは、この情報の一部は他のサービスで必要になる場合がありますが、州の店舗を完全に再現することは実用的ではありません。より良い代替アプローチは、フックを使用することです。たとえば、別のデータベース内にすべての部屋のメッセージを保存するには、 roomMessageAfterフックだけを使用できます。また、 ServiceAPI 、バックエンドメッセージングバスを介して他の内部サーバーに公開できます。
通常の状況では、サービスユーザーに返されるすべてのエラー(リクエスト応答を介して、 loginConfirmedまたはloginRejectedメッセージ)は、 ChatServiceErrorのインスタンスです。他のすべてのエラーは、プログラムのバグまたはサービスインフラストラクチャの障害を示しています。このようなエラーのデバッグロギングを有効にするにはexport NODE_DEBUG=ChatServiceを使用します。ライブラリはBlueBird ^3.0.0約束を使用しているため、長いスタックトレースを有効にするにはexport BLUEBIRD_DEBUG=1使用します。フックにAPIのPromiseバージョンを使用し、フックを返すためにChatServiceErrorサブクラスを使用することを強くお勧めします。
サーバーサイドAPIおよびRPCドキュメントはオンラインで入手できます。
サービスは、ユーザーの概念から接続の概念を完全に抽象化するため、1人のユーザーが複数の接続(異なるノードの接続を含む)を含むことができます。ユーザーの存在の場合、結合されたソケットの数はゼロより大きくなければなりません。すべてのAPIがユーザーレベルで動作するように設計され、ユーザーの複数の接続をシームレスに処理します。
接続は完全に独立しており、追加のクライアントサイドサポートは必要ありません。ただし、他のユーザーの接続に関する情報を取得するために使用できる情報メッセージとコマンドがあります。すべての接続を同じ部屋に結合するようにするなど、クライアント側の同期パターンを実現することが可能になります。
各部屋には許可システムがあります。すべての管理者特権を持ち、ユーザーを管理者グループに割り当てることができる1人の所有者ユーザーがいます。管理者は、他のユーザーのアクセス許可を管理できます。ブラックリストとホワイトリストの2つのモードがサポートされています。アクセスリスト/モード変更の後、サービスはアクセス許可を失ったユーザーを自動的に削除します。
enableRoomsManagementオプションが有効になっている場合、ユーザーはroomCreateコマンドを介して部屋を作成できます。部屋の作成者は所有者であり、 roomDeleteコマンドで削除することもできます。
フックを使用する前に、追加のアクセス許可システムを実装できます。
ユーザーが部屋のメッセージを送信すると、RPCでメッセージidが返されます。これは、メッセージがストアに保存されていることを意味します(構造のような円形のバッファーのみ)。ルームメッセージIDは1から始まるシーケンスであり、部屋で正常に送信されたメッセージごとに1つ増加します。クライアントは、 roomHistoryInfoコマンドを介して常に最後のルームメッセージIDを確認し、 roomHistoryGetコマンドを使用して欠落しているメッセージを取得できます。このようなアプローチは、回転のために削除されない限り、メッセージを受信できることを保証します。
デフォルトでは、クライアントは{textMessage: 'Some string'}に限定されたメッセージを送信できます。カスタムメッセージフォーマットを有効にするには、 directMessagesCheckerまたはroomMessagesCheckerフックを提供します。フックが解決すると、メッセージ形式が受け入れられます。メッセージは、いくつかの制限を伴う任意のデータにすることができます。トップレベルは、 timestamp 、 author 、またはidフィールドのないObjectでなければなりません(メッセージを送信する前に、サービスはこのフィールドを埋めます)。ネストされたレベルには、任意のデータ型(バイナリも)を含めることができますが、 'Buffer'に設定されたフィールドtypeを持つネストされたオブジェクトはありません(バイナリデータ操作に使用)。
各ユーザーコマンドは、フックの追加の前後にサポートされ、クライアント接続/切断フックもサポートされています。コマンドとフックは順番に実行されます:fook -command -affery fook(コマンドエラーでも呼び出されます)。フック前のシーケンス終了が可能です。クライアントは追加のコマンド引数を送信し、フックはそれらを読み取り、追加の引数で返信できます。
ユーザーコマンドサーバーサイドexecUserCommandを実行するために提供されます。また、 ServiceAPIとTransportInterfaceによって提供されるサーバーのみのみの方法もあります。カスタマイズの例でいくつかのカスタマイズケースを探してください。
サービスにより、ストアにユーザーの存在と接続データが保持されます。これは、永続的または共有される場合があります。したがって、インスタンスが誤ってシャットダウンされている場合(電話をかけたり、終了するのを待機せcloseに)、ストアへの完全なネットワーク接続を失った場合、存在データが間違っています。このケースを修正するにはinstanceRecoveryメソッドが提供されます。
また、接続依存のデータの一貫性に関するより微妙なケースがあります。輸送通信インスタンスとストアインスタンスは、さまざまな種類のネットワーク、ソフトウェア、またはハードウェアの障害を体験できます。一部のエッジの場合(複数のユーザーの操作など)、このような障害は矛盾を引き起こす可能性があります(ほとんどの場合、コマンドの発行者にエラーが返されます)。これらのイベントは、インスタンスエミッター( storeConsistencyFailureイベントなど)を介して報告され、データはRecoveryAPIメソッドを介して同期できます。
デフォルトでは、すべてのユーザーには一意のログイン(ユーザー名)があると想定されています。名前の生成を管理する代わりに、個別のトランスポートとの統合を使用できます(または、たとえば別のsocket.ioネームスペースなど)。ルームメッセージは、フック後のroomMessageからトランスポートに転送できます。これは、ログインなしでアクセスできます。また、一部のサービスコマンドは、 execUserCommandを介して匿名ユーザーによって実行されることができます。
フック後のroomMessageを使用して、ある部屋から別の部屋にメッセージを転送することもできます。そのため、部屋は別の部屋からのメッセージ集約に使用できます。フックは単なる機能であり、メッセージコンテンツに完全にアクセスできるため、任意のコンテンツベースの転送ルールを実装できます。高度にパーソナライズされたユーザー(クライアント)固有のフィードを使用したシステムの実装を含む。
デフォルトでは、他のユーザーが部屋に参加するユーザー接続の数と種類を知る方法はありません。このような情報は、たとえばクエリ文字列で渡すことができ、接続フックを介して保存できます。この発表は、直接輸送sendToChannelメソッドを使用して、 onJoinおよびonLeaveフックで行うことができます。また、接続されたデバイスの種類に関する追加情報は、フック後にroomGetAccessListから送信する必要があります(リスト名が'userlist'に等しい場合)。
削除または編集操作はありません。これは、部屋の歴史の中で矛盾を作るためです。削除と編集の一般的な代替手段は、クライアントがメッセージを非表示または変更するために使用する特別な意味でルームメッセージを使用することです。
このパッケージでバグに遭遇した場合は、GitHub Repoの問題にバグレポートを提出してください。
PRも受け入れられます。
mit