Le sous-protocole protobuf WebSocket pris en charge par Azure Web PubSub

Ce document décrit le sous-protocole protobuf.webpubsub.azure.v1.

Lorsqu’un client utilise ce sous-protocole, les trames de données entrantes et sortantes sont supposées être des charges utiles de mémoires tampon de protocole (protobuf).

Vue d’ensemble

Le sous-protocole protobuf.webpubsub.azure.v1 permet aux clients de publier/s’abonner (PubSub) directement au lieu d’effectuer un aller-retour au serveur en amont. La connexion WebSocket avec le sous-protocole protobuf.webpubsub.azure.v1 est appelée un client WebSocket PubSub.

Par exemple, dans JavaScript, vous pouvez créer un client WebSocket PubSub avec le sous-protocole protobuf à l’aide des éléments suivants :

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

Pour un client WebSocket simple, le serveur a le rôle nécessaire de gestion des événements des clients. Une connexion WebSocket simple déclenche toujours un événement message lors de l’envoi de messages, et il s’appuie toujours sur le côté serveur pour traiter les messages et effectuer d’autres opérations. Avec l’aide du sous-protocole protobuf.webpubsub.azure.v1, un client autorisé peut rejoindre un groupe avec des requêtes d’adhésion et publier des messages directement dans un groupe avec des requêtes de publication. Le client peut également router des messages vers différents gestionnaires d’événements en amont avec des requêtes d’événement pour personnaliser l’événement auquel le message appartient.

Remarque

Actuellement, le service Web PubSub ne prend en charge que proto3.

autorisations

Un client PubSub WebSocket peut publier sur d’autres clients seulement s’il est autorisé. Les roles attribués au client déterminent les autorisations accordées au client :

Rôle Autorisation
Non spécifié(e) Le client peut envoyer des requêtes d’événements.
webpubsub.joinLeaveGroup Le client peut rejoindre/quitter n’importe quel groupe.
webpubsub.sendToGroup Le client peut publier des messages dans n’importe quel groupe.
webpubsub.joinLeaveGroup.<group> Le client peut rejoindre/quitter le groupe <group>.
webpubsub.sendToGroup.<group> Le client peut publier des messages sur le groupe <group>.
webpubsub.joinLeaveGroups.<pattern> Le client peut joindre/quitter n’importe quel groupe dont le nom correspond <pattern> (voir Modèles de rôle de groupe générique).
webpubsub.sendToGroups.<pattern> Le client peut publier des messages sur n’importe quel groupe dont le nom correspond <pattern> (voir Modèles de rôle de groupe générique).

Le serveur peut accorder ou révoquer les autorisations d’un client de manière dynamique en utilisant des API REST ou des SDK de serveur.

Remarque

Les rôles génériques (par exemple, webpubsub.sendToGroups.<pattern>) ne sont pas pris en charge dans les API REST ou les kits SDK serveur pendant l’exécution.

Demandes

Tous les messages de requête adhèrent au format protobuf suivant :

syntax = "proto3";

import "google/protobuf/any.proto";

message UpstreamMessage {
    oneof message {
        SendToGroupMessage send_to_group_message = 1;
        EventMessage event_message = 5;
        JoinGroupMessage join_group_message = 6;
        LeaveGroupMessage leave_group_message = 7;
        SequenceAckMessage sequence_ack_message = 8;
        PingMessage ping_message = 9;
        StreamDataMessage stream_data_message = 13;
        StreamEndMessage stream_end_message = 14;
    }

    message SendToGroupMessage {
        string group = 1;
        optional uint64 ack_id = 2;
        MessageData data = 3;
        optional bool no_echo = 4;
        StreamStartInfo stream = 7;
    }

    message StreamStartInfo {
        string stream_id = 1;
        optional uint32 idle_timeout_ms = 2;
    }

    message EventMessage {
        string event = 1;
        MessageData data = 2;
        optional uint64 ack_id = 3;
    }
    
    message JoinGroupMessage {
        string group = 1;
        optional uint64 ack_id = 2;
    }

    message LeaveGroupMessage {
        string group = 1;
        optional uint64 ack_id = 2;
    }

    message SequenceAckMessage {
        uint64 sequence_id = 1;
    }

    message PingMessage {
    }

    message StreamDataMessage {
        string stream_id = 1;
        optional uint64 stream_sequence_id = 2;
        MessageData data = 3;
    }

    message StreamEndMessage {
        string stream_id = 1;
        optional StreamEndError error = 2;

        message StreamEndError {
            optional string message = 1;
            optional string user_error_code = 2;
        }
    }
}

message MessageData {
    oneof data {
        string text_data = 1;
        bytes binary_data = 2;
        google.protobuf.Any protobuf_data = 3;
    }
}

Rejoindre des groupes

Format :

Définissez join_group_message.group sur le nom du groupe.

  • ackId est l’identité de chaque demande et doit être unique. Le service envoie un message de réponse Ack pour notifier le résultat du processus de la demande. Pour plus d’informations, consultez AckId et réponse Ack.

Quitter des groupes

Format :

Définissez leave_group_message.group sur le nom du groupe.

  • ackId est l’identité de chaque demande et doit être unique. Le service envoie un message de réponse Ack pour notifier le résultat du processus de la demande. Pour plus d’informations, consultez AckId et réponse Ack.

Publier des messages

Format :

  • ackId : identité unique de chaque demande. Le service envoie un message de réponse d’accusé de réception (ack) pour notifier le résultat du processus de la demande. Pour plus d’informations, consultez AckId et réponse Ack.

  • dataType : le format de données, qui peut être protobuf, text ou binary selon les data dans MessageData. Les clients destinataires peuvent utiliser dataType pour traiter correctement le contenu.

  • protobuf : si vous définissez send_to_group_message.data.protobuf_data, la valeur implicite de dataType est protobuf. protobuf_data peut être Tout type de message. Tous les autres clients reçoivent un binaire encodé en protobuf, qui peut être désérialisé par le kit de développement logiciel (SDK) protobuf. Les clients qui ne prennent en charge que le contenu texte (par exemple, json.webpubsub.azure.v1) reçoivent un binaire encodé en Base64.

  • text : si vous définissez send_to_group_message.data.text_data, la valeur implicite de dataType est text. text_data doit être une chaîne. Tous les clients avec d’autres protocoles reçoivent une chaîne encodée en UTF-8.

  • binary : si vous définissez send_to_group_message.data.binary_data, la valeur implicite de dataType est binary. binary_data doit être un tableau d’octets. Tous les clients avec d’autres protocoles reçoivent un binaire brut sans encodage protobuf. Les clients qui ne prennent en charge que le contenu texte (par exemple, json.webpubsub.azure.v1) reçoivent un binaire encodé en Base64.

Cas 1 : publier des données texte

Définissez send_to_group_message.group sur group, et send_to_group_message.data.text_data sur "text data".

  • Le client de sous-protocole protobuf du groupe group reçoit la trame binaire et peut utiliser DownstreamMessage pour la désérialiser.

  • Les clients du sous-protocole JSON dans group reçoivent :

    {
        "type": "message",
        "from": "group",
        "group": "group",
        "dataType" : "text",
        "data" : "text data"
    }
    
  • Les clients WebSocket simples dans group reçoivent la chaîne text data.

Cas 2 : publier des données protobuf

Supposons que vous avez un message de client :

message MyMessage {
    int32 value = 1;
}

Définissez send_to_group_message.group sur group, et send_to_group_message.data.protobuf_data sur Any.pack(MyMessage) avec value = 1.

  • Le client de sous-protocole protobuf dans group reçoit la trame binaire et peut utiliser DownstreamMessage pour la désérialiser.

  • Le client de sous-protocole dans group reçoit :

    {
        "type": "message",
        "from": "group",
        "group": "G",
        "dataType" : "protobuf",
        "data" : "Ci90eXBlLmdvb2dsZWFwaXMuY29tL2F6dXJlLndlYnB1YnN1Yi5UZXN0TWVzc2FnZRICCAE=" // Base64-encoded bytes
    }
    

    Remarque

    Les données correspondent à un binaire protobuf désérialisable, encodé en Base64.

Vous pouvez utiliser la définition protobuf suivante et la désérialiser avec Any.unpack() :

syntax = "proto3";

message MyMessage {
    int32 value = 1;
}
  • Les clients WebSocket simples dans group reçoivent la trame binaire :

    # Show in hexadecimal
    0A 2F 74 79 70 65 2E 67 6F 6F 67 6C 65 61 70 69 73 2E 63 6F 6D 2F 61 7A 75 72 65 2E 77 65 62 70 75 62 73 75 62 2E 54 65 73 74 4D 65 73 73 61 67 65 12 02 08 01
    

Cas 3 : publier des données binaires

Définissez send_to_group_message.group sur group, et send_to_group_message.data.binary_data sur [1, 2, 3].

  • Le client de sous-protocole protobuf du groupe group reçoit la trame binaire et peut utiliser DownstreamMessage pour la désérialiser.

  • Le client de sous-protocole JSON du groupe group reçoit :

    {
        "type": "message",
        "from": "group",
        "group": "group",
        "dataType" : "binary",
        "data" : "AQID", // Base64-encoded [1,2,3]
    }
    

    Étant donné que le client de sous-protocole JSON ne prend en charge que la messagerie texte, le binaire est toujours encodé en Base64.

  • Les clients WebSocket simples dans group reçoivent les données binaires dans la trame binaire :

    # Show in hexadecimal
    01 02 03
    

Commencez à diffuser les messages

Pour lancer un flux de groupe, définissez send_to_group_message.group sur le groupe cible et définissez send_to_group_message.stream sur un StreamStartInfo message. Une demande de démarrage de flux ne se configure send_to_group_message.data ni send_to_group_message.ack_id.

  • send_to_group_message.stream.stream_id est l’identifiant du flux logique. Il doit s’agir d’une chaîne non vide et doit être unique parmi les flux actifs sur la même connexion client. Il est recommandé aux bibliothèques clients de générer une valeur globalement unique, comme un GUID ou un UUID.
  • send_to_group_message.stream.idle_timeout_ms est facultatif. Si spécifié, il doit être supérieur à 0. Si elle est omise, le service par défaut est 300000 des millisecondes. La valeur correspond à un délai d’attente inactif, pas à une durée totale de vie du flux. Envoyez les données du flux, envoyez un flux en mode « keep epalive », ou terminez le flux avant que ce délai d’expiration ne s’écoule, lorsque l’application doit garder le flux ouvert.
  • send_to_group_message.no_echo est facultatif. Si elle est réglée sur true, les messages du flux ne sont pas renvoyés vers la même connexion. Si elle n’est pas définie, la valeur par défaut est false.

Lorsque le flux est accepté, le client reçoit une réponse d’ack du flux avec expected_sequence_id un objectif fixé à 1.

Envoyer des données en streaming

Pour envoyer des données de flux, définissez stream_data_message.stream_id, stream_data_message.stream_sequence_id, et stream_data_message.data.

  • stream_data_message.stream_id identifie un flux actif sur la même connexion client.
  • stream_data_message.stream_sequence_id est un nombre uint64 positif. Le premier fragment de données d’un flux utilise 1, et chaque fragment de données suivant pour le même stream_id s’augmente exactement 1de .
  • stream_data_message.data utilise les mêmes MessageData règles d’encodage que les messages publiés.

Pour maintenir un flux actif sans fournir de données aux abonnés, envoyez stream_data_message avec seulement stream_id défini.

Fin des messages en streaming

Pour terminer un flux, on met stream_end_message.stream_id.

Pour terminer un flux avec une erreur définie par l’application, on définit stream_end_message.error.

  • stream_end_message.error.message est un message d’erreur lisible par l’humain optionnel.
  • stream_end_message.error.user_error_code est un code d’erreur optionnel défini par l’application.

Lorsque le flux est fermé, l’éditeur reçoit une réponse de fermeture du flux.

Envoyer des événements personnalisés

Il existe un dataType implicite, qui peut être protobuf, text ou binary, en fonction du dataType que vous définissez. Les clients récepteurs peuvent utiliser dataType pour traiter correctement le contenu.

  • protobuf : si vous définissez event_message.data.protobuf_data, la valeur implicite de dataType est protobuf. La valeur de protobuf_data peut être n’importe quel type protobuf pris en charge. Le gestionnaire d’événements reçoit le binaire encodé en protobuf, qui peut être désérialisé par n’importe quel kit de développement logiciel (SDK) protobuf.

  • text : si vous définissez event_message.data.text_data, la valeur implicite de dataType est text. La valeur de text_data doit être une chaîne. Le gestionnaire d’événements reçoit une chaîne encodée en UTF-8.

  • binary : si vous définissez event_message.data.binary_data, la valeur implicite de dataType est binary. La valeur de binary_data doit être un tableau d’octets. Le gestionnaire d’événements reçoit la trame binaire brute.

Cas 1 : envoyer un événement avec des données texte

Affectez la valeur event_message.data.text_data à "text data".

Le gestionnaire d’événements en amont reçoit une demande semblable à la suivante :

POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: text/plain
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>

text data

Le Content-Type de la requête HTTP CloudEvents est text/plain, où dataType=text.

Cas 2 : envoyer un événement avec des données protobuf

Supposons que vous avez reçu le message de client suivant :

message MyMessage {
    int32 value = 1;
}

Définissez event_message.data.protobuf_data sur any.pack(MyMessage) avec value = 1

Le gestionnaire d’événements en amont reçoit une demande semblable à la suivante :

POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/json
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>

// Just show in hexadecimal; read it as binary
0A 2F 74 79 70 65 2E 67 6F 6F 67 6C 65 61 70 69 73 2E 63 6F 6D 2F 61 7A 75 72 65 2E 77 65 62 70 75 62 73 75 62 2E 54 65 73 74 4D 65 73 73 61 67 65 12 02 08 01

Le Content-Type de la requête HTTP CloudEvents est application/x-protobuf, où dataType=protobuf.

Les données correspondent à un binaire protobuf valide. Vous pouvez utiliser les paramètres proto et any.unpack() suivants pour le désérialiser :

syntax = "proto3";

message MyMessage {
    int32 value = 1;
}

Cas 3 : envoyer un événement avec des données binaires

Affectez la valeur send_to_group_message.binary_data à [1, 2, 3].

Le gestionnaire d’événements en amont reçoit une demande semblable à la suivante :

POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/octet-stream
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>

// Just show in hexadecimal; you need to read it as binary
01 02 03 

Pour dataType=binary, la valeur de Content-Type pour la requête HTTP CloudEvents est application/octet-stream. La trame WebSocket peut être au format text pour les trames de message texte ou de binaires encodés en UTF-8 pour les trames de message binary.

Le service refuse le client si le message ne correspond pas au format prescrit.

Ping

Le client peut envoyer un PingMessage au service pour permettre au service Web PubSub de détecter si le client est actif.

Réponses

Tous les messages de réponse adhèrent au format protobuf suivant :

message DownstreamMessage {
    oneof message {
        AckMessage ack_message = 1;
        DataMessage data_message = 2;
        SystemMessage system_message = 3;
        PongMessage pong_message = 4;
        StreamAckMessage stream_ack_message = 6;
        StreamNackMessage stream_nack_message = 7;
        StreamClosedMessage stream_closed_message = 8;
    }
    
    message AckMessage {
        uint64 ack_id = 1;
        bool success = 2;
        optional ErrorMessage error = 3;
    
        message ErrorMessage {
            string name = 1;
            string message = 2;
        }
    }

    message DataMessage {
        string from = 1;
        optional string group = 2;
        MessageData data = 3;
        StreamInfo stream = 6;
    }

    message SystemMessage {
        oneof message {
            ConnectedMessage connected_message = 1;
            DisconnectedMessage disconnected_message = 2;
        }
    
        message ConnectedMessage {
            string connection_id = 1;
            string user_id = 2;
        }

        message DisconnectedMessage {
            string reason = 2;
        }
    }

    message PongMessage {
    }

    message StreamAckMessage {
        string stream_id = 1;
        uint64 expected_sequence_id = 2;
    }

    message StreamNackMessage {
        string stream_id = 1;
        string name = 2;
        string message = 3;
        uint64 expected_sequence_id = 4;
    }

    message StreamClosedMessage {
        string stream_id = 1;
        optional StreamClosedError error = 2;

        message StreamClosedError {
            string name = 1;
            string message = 2;
        }
    }
}

message StreamInfo {
    string stream_id = 1;
    uint64 stream_sequence_id = 2;
    optional bool end_of_stream = 3;
    optional StreamError error = 4;

    message StreamError {
        string name = 1;
        string message = 2;
        string user_error_code = 3;
    }
}

Les messages reçus par le client peuvent être ack, message, system, pongstreamAck, streamNack, , ou streamClosed.

Réponse ACK

Si la requête contient ackId, le service renvoie une réponse ACK pour cette requête. L’implémentation du client doit gérer ce mécanisme d’acquittement (ack), y compris :

  • Attendre la réponse ACK pour une opération asyncawait.
  • Vérification du délai d’attente lorsque la réponse ACK n’est pas reçue au cours d’une certaine période.

L’implémentation du client doit toujours d’abord vérifier si l’état success est true ou false. Lorsque l’état success est false, le client peut lire les détails de l’erreur dans la propriété error.

Réponse de message

Les clients peuvent recevoir des messages publiés à partir d’un groupe auquel le client s’est joint. Autrement, ils peuvent recevoir des messages du rôle gestion de serveur lorsque le serveur envoie des messages à un client spécifique ou à un utilisateur spécifique.

Vous obtiendrez toujours un message DownstreamMessage.DataMessage dans les scénarios suivants :

  • Lorsque le message provient d’un groupe, la valeur de from est group. Lorsque le message provient du serveur, la valeur de from est server.
  • Lorsque le message provient d’un groupe, la valeur de group est le nom du groupe.

Le paramètre dataType de l’expéditeur entraînera l’envoi de l’un des messages suivants :

  • Si la valeur de dataType est text, utilisez message_response_message.data.text_data.
  • Si la valeur de dataType est binary, utilisez message_response_message.data.binary_data.
  • Si la valeur de dataType est protobuf, utilisez message_response_message.data.protobuf_data.
  • Si la valeur de dataType est json, utilisez message_response_message.data.text_data, et le contenu est une chaîne JSON sérialisée.

Lorsqu’un message de groupe appartient à un flux, DownstreamMessage.DataMessage.stream est défini.

  • stream.stream_id est l’identifiant logique du flux.
  • stream.stream_sequence_id est le numéro de séquence du message dans le flux.
  • stream.end_of_stream est facultatif. Lorsqu’il est réglé à true, le message est le message terminal du flux.
  • stream.error est optionnel et n’est présent que lorsque le flux se termine avec une erreur. user_error_code est présent uniquement pour UserError.

Réponse des flux d’eau

Le service envoie DownstreamMessage.StreamAckMessage pour accuser réception des données de flux acceptées et pour signaler l’ID de la prochaine séquence de flux qu’il attend.

  • stream_id est l’identifiant logique du flux.
  • expected_sequence_id est l’ID de la prochaine séquence de flux que le service attend.

Réponse du ruisseau

Le service envoie DownstreamMessage.StreamNackMessage une erreur de flux réessayable.

  • stream_id est l’identifiant logique du flux.
  • expected_sequence_id est l’ID de la prochaine séquence de flux que le service attend.
  • name peut avoir la valeur InvalidSequenceId ou TransientError.
  • message contient les détails de l’erreur.

Réponse fermée par flux

Le service est envoyé DownstreamMessage.StreamClosedMessage lorsque le flux côté éditeur est fermé.

  • stream_id est l’identifiant logique du flux.
  • error est omis lorsque le flux est normalement fermé.
  • error.name peut être StreamNotFound, Forbidden, BadRequest, InternalServerError, ou IdleTimeout.
  • error.message contient les détails de l’erreur.

Réponse du système

Le service Web PubSub peut également envoyer au client des réponses liées au système.

Connecté

Lorsque le client se connecte au service, vous recevez un message DownstreamMessage.SystemMessage.ConnectedMessage.

Déconnecté

Lorsque le serveur clôt la connexion ou que le service refuse le client, vous recevez un message DownstreamMessage.SystemMessage.DisconnectedMessage.

Réponse Pong

Le service Web PubSub envoie un PongMessage au client lorsqu’il reçoit un PingMessage de sa part.

Étapes suivantes

Utilisez ces ressources pour commencer à créer votre propre application :