サブプロトコルを使用して信頼性の高い WebSocket を作成する

WebSocket クライアント接続は、断続的なネットワークの問題が原因で切断されることがあります。接続が切断された場合、メッセージも失われます。 PubSub (発行と購読) システムでは、パブリッシャーとサブスクライバーが分離されているため、パブリッシャーは、サブスクライバーの切断やメッセージの喪失を検出する必要があります。 断続的なネットワークの問題を克服し、メッセージ配信の信頼性を維持することが、クライアントには欠かせません。 リライアブル サブプロトコルを利用すれば、信頼性の高い WebSocket クライアントを作成してそれを実現できます。

注意

リライアブル プロトコルはまだプレビュー段階です。 将来、いくつかの変更が予定されています。

リライアブル プロトコル

サービスでは、json.reliable.webpubsub.azure.v1protobuf.reliable.webpubsub.azure.v1 の 2 つのリライアブル サブプロトコルがサポートされます。 クライアントは、プロトコルに準拠する必要があります。特に、信頼性を実現する再接続、パブリッシャー、サブスクライバーの部分に準拠しないと、メッセージ配信が適切に機能しなかったり、クライアントがプロトコル仕様違反でサービスによって強制的に終了されたりする場合があります。

初期化

リライアブル サブプロトコルを使用するためには、WebSocket 接続を構築するときにサブプロトコルを設定する必要があります。 JavaScript では、次のように使用できます。

  • Json リライアブル サブプロトコルを使用する

    var pubsub = new WebSocket('wss://test.webpubsub.azure.com/client/hubs/hub1', 'json.reliable.webpubsub.azure.v1');
    
  • Protobuf リライアブル サブプロトコルを使用する

    var pubsub = new WebSocket('wss://test.webpubsub.azure.com/client/hubs/hub1', 'protobuf.reliable.webpubsub.azure.v1');
    

再接続

WebSocket 接続は TCP に依存しているため、接続が切断されなければ、すべてのメッセージは損失なく、かつ正しい順序になっています。 ネットワークの問題や接続の切断が発生した場合は、グループやメッセージ情報など、すべての状態はサービスが維持し、再接続を待って回復します。 WebSocket 接続は、セッションをサービス内で所有し、その識別子は connectionId になります。 再接続は信頼性を実現するうえでの基盤であるため、必ず実装しなければなりません。 リライアブル サブプロトコルを使用してサービスに接続するとき、新しい接続は、connectionIdreconnectionToken を含んだ Connected メッセージを受け取ります。

{
    "type":"system",
    "event":"connected",
    "connectionId": "<connection_id>",
    "reconnectionToken": "<reconnection_token>"
}

WebSocket 接続が切断された後、クライアントはセッションを維持するために、まず同じ connectionId を使用して再接続を試みる必要があります。 クライアントがサーバーとネゴシエートして access_token を取得する必要はありません。 その代わりに再接続では、connection_idreconnection_token を使用し、次の URI で直接、サービスに対する WebSocket 接続要求を行う必要があります。

wss://<service-endpoint>/client/hubs/<hub>?awps_connection_id=<connection_id>&awps_reconnection_token=<reconnection_token>

ネットワークの問題がまだ解消していなければ再接続は失敗する可能性があります。 クライアントは、次の状況になるまで再接続を繰り返し試みる必要があります。

  1. WebSocket 接続が状態コード 1008 で閉じる。 この状態コードは connectionId がサービスから削除されたことを意味します。
  2. 再接続に失敗する状況が 1 分以上続く。

Publisher

このドキュメントでは、イベント ハンドラーにイベントを送信したり他のクライアントにメッセージを発行したりするクライアントをパブリッシャーと呼びます。 パブリッシャーは、メッセージの発行に成功したかどうかについての確認応答をサービスから受け取るために、メッセージに ackId を設定する必要があります。 メッセージ内の ackId は、メッセージの ID です。つまり、メッセージが異なれば、使用される ackId も異なります。一方、メッセージの再送信では、サービスが重複を除去するために同じ ackId を維持する必要があります。

グループ送信メッセージのサンプル:

{
    "type": "sendToGroup",
    "group": "group1",
    "dataType" : "text",
    "data": "text data",
    "ackId": 1
}

確認応答のサンプル:

{
    "type": "ack",
    "ackId": 1,
    "success": true
}

success: true を含む確認応答がサービスから返された場合、メッセージはサービスによって既に処理されています。すべてのサブスクライバーにメッセージが配信されるであろうという見込みをクライアントは得ることができます。

ただし場合によっては、なんらかの一時的な内部エラーがサービスで発生し、メッセージをサブスクライバーに送信できないこともあります。 そのような場合、パブリッシャーには次のような確認応答が返されます。ビジネス ロジックに応じて必要であれば、パブリッシャーは同じ ackId でメッセージを再送信する必要があります。

{
    "type": "ack",
    "ackId": 1,
    "success": false,
    "error": {
        "name": "InternalServerError",
        "message": "Internal server error"
    }
}

Message Failure

WebSocket 接続の切断が原因でサービスの確認応答がドロップされる可能性があります。 そのため、WebSocket 接続が切断されたときは、パブリッシャーが通知を受け取り、再接続後に同じ ackId でメッセージを再送信する必要があります。 実際にはメッセージがサービスによって処理されていた場合、サービスは Duplicate で確認応答し、パブリッシャーは、そのメッセージの再送信を停止する必要があります。

{
    "type": "ack",
    "ackId": 1,
    "success": false,
    "error": {
        "name": "Duplicate",
        "message": "Message with ack-id: 1 has been processed"
    }
}

Message duplicated

サブスクライバー (Subscriber)

このドキュメントでは、イベント ハンドラーまたはパブリッシャーから送信されたメッセージを受信するクライアントを "サブスクライバー" と呼びます。 ネットワークの問題によって接続が切断されると、サブスクライバーに対して実際に送信されたメッセージの数が、サービスにはわからなくなります。 そのためサブスクライバーは、受信済みのメッセージをサービスに通知する必要があります。 データ メッセージには sequenceId が含まれているので、サブスクライバーは、シーケンス ACK メッセージでシーケンス ID を確認応答する必要があります。

シーケンス ACK のサンプル:

{
    "type": "sequenceAck",
    "sequenceId": 1
}

シーケンス ID は、connection-id セッション内の増分値 (uint64の数値) です。 サブスクライバーは、過去に受信した最大 sequence-id を記録しておき、sequence-id がそれよりも大きいメッセージはすべて受け取り、それ以下のメッセージはすべて破棄する必要があります。シーケンス ACK は、確認応答の累積をサポートします。つまり、確認応答として sequence-id=5 を送信した場合、サービスは、sequence-id が 5 未満のメッセージはすべてサブスクライバーが受信済みと見なします。 サブスクライバーは、記録済みの最大 sequence-id を確認応答する必要があります。そうすることで、既にサブスクライバーで受信済みとなっているメッセージの再配信をサービスがスキップできるようになります。

すべてのメッセージは、WebSocket 接続が切断されるまで、サブスクライバーに配信されます。 sequence-id があることで、サービスは、WebSocket 接続の切断を挟んだ特定の connection-id セッション内でサブスクライバーが実際に受信したメッセージの数を把握できます。 WebSocket 接続の切断後、サービスが配信すべきメッセージのうち、サブスクライバーからの確認応答がないメッセージが再配信されます。 確認応答がないメッセージは保留状態となりますが、制限があります。その制限をメッセージが超えた場合、サービスによって WebSocket 接続が閉じられ、connection-id セッションは削除されます。 したがってサブスクライバーは、できるだけ速やかに sequence-id を確認応答する必要があります。