The Azure Web PubSub-supported protobuf WebSocket subprotocol
This document describes the subprotocol protobuf.webpubsub.azure.v1
.
When a client is using this subprotocol, both the outgoing and incoming data frames are expected to be protocol buffers (protobuf) payloads.
Overview
Subprotocol protobuf.webpubsub.azure.v1
empowers the client to do a publish-subscribe (PubSub) directly instead of doing a round trip to the upstream server. The WebSocket connection with the protobuf.webpubsub.azure.v1
subprotocol is called a PubSub WebSocket client.
For example, in JavaScript, you can create a PubSub WebSocket client with the protobuf subprotocol by using:
// PubSub WebSocket client
var pubsub = new WebSocket('wss://test.webpubsub.azure.com/client/hubs/hub1', 'protobuf.webpubsub.azure.v1');
For a simple WebSocket client, the server has the necessary role of handling events from clients. A simple WebSocket connection always triggers a message
event when it sends messages, and it always relies on the server side to process messages and do other operations. With the help of the protobuf.webpubsub.azure.v1
subprotocol, an authorized client can join a group by using join requests and publish messages to a group by using publish requests directly. The client can also route messages to various upstream event handlers by using event requests to customize the event that the message belongs to.
Note
Currently, the Web PubSub service supports only proto3.
Permissions
A PubSub WebSocket client can only publish to other clients when it's authorized. The roles
assigned to the client determine the permissions granted to the client:
Role | Permission |
---|---|
Not specified | The client can send event requests. |
webpubsub.joinLeaveGroup |
The client can join/leave any group. |
webpubsub.sendToGroup |
The client can publish messages to any group. |
webpubsub.joinLeaveGroup.<group> |
The client can join/leave the group <group> . |
webpubsub.sendToGroup.<group> |
The client can publish messages to the group <group> . |
The server can dynamically grant or revoke client permissions through REST APIs or server SDKs.
Requests
All request messages adhere to the following protobuf format:
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;
}
message SendToGroupMessage {
string group = 1;
optional uint64 ack_id = 2;
MessageData data = 3;
}
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 PingMessage {
}
}
message MessageData {
oneof data {
string text_data = 1;
bytes binary_data = 2;
google.protobuf.Any protobuf_data = 3;
}
}
Join groups
Format:
Set join_group_message.group
to the group name.
ackId
is the identity of each request and should be unique. The service sends a ack response message to notify the process result of the request. More details can be found at AckId and Ack Response
Leave groups
Format:
Set leave_group_message.group
to the group name.
ackId
is the identity of each request and should be unique. The service sends a ack response message to notify the process result of the request. More details can be found at AckId and Ack Response
Publish messages
Format:
ackId
: The unique identity of each request. The service sends an ack response message to notify the process result of the request. More details can be found at AckId and Ack ResponsedataType
: The data format, which can beprotobuf
,text
, orbinary
depending on thedata
inMessageData
. The receiving clients can usedataType
to correctly process the content.protobuf
: When you setsend_to_group_message.data.protobuf_data
, the implicitdataType
isprotobuf
.protobuf_data
can be of the Any message type. All other clients receive a protobuf-encoded binary, which can be deserialized by the protobuf SDK. Clients that support only text-based content (for example,json.webpubsub.azure.v1
) receive a Base64-encoded binary.text
: When you setsend_to_group_message.data.text_data
, the implicitdataType
istext
.text_data
should be a string. All clients with other protocols receive a UTF-8-encoded string.binary
: When you setsend_to_group_message.data.binary_data
, the implicitdataType
isbinary
.binary_data
should be a byte array. All clients with other protocols receive a raw binary without protobuf encoding. Clients that support only text-based content (for example,json.webpubsub.azure.v1
) receive a Base64-encoded binary.
Case 1: Publish text data
Set send_to_group_message.group
to group
, and set send_to_group_message.data.text_data
to "text data"
.
The protobuf subprotocol client in group
group
receives the binary frame and can use DownstreamMessage to deserialize it.The JSON subprotocol clients in
group
receive:{ "type": "message", "from": "group", "group": "group", "dataType" : "text", "data" : "text data" }
The simple WebSocket clients in
group
receive stringtext data
.
Case 2: Publish protobuf data
Let's assume that you have a custom message:
message MyMessage {
int32 value = 1;
}
Set send_to_group_message.group
to group
and send_to_group_message.data.protobuf_data
to Any.pack(MyMessage)
with value = 1
.
The protobuf subprotocol clients in
group
receive the binary frame and can use DownstreamMessage to deserialize it.The subprotocol client in
group
receives:{ "type": "message", "from": "group", "group": "G", "dataType" : "protobuf", "data" : "Ci90eXBlLmdvb2dsZWFwaXMuY29tL2F6dXJlLndlYnB1YnN1Yi5UZXN0TWVzc2FnZRICCAE=" // Base64-encoded bytes }
Note
The data is a Base64-encoded, deserializeable protobuf binary.
You can use the following protobuf definition and use Any.unpack()
to deserialize it:
syntax = "proto3";
message MyMessage {
int32 value = 1;
}
The simple WebSocket clients in
group
receive the binary frame:# 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
Case 3: Publish binary data
Set send_to_group_message.group
to group
, and set send_to_group_message.data.binary_data
to [1, 2, 3]
.
The protobuf subprotocol client in group
group
receives the binary frame and can use DownstreamMessage to deserialize it.The JSON subprotocol client in group
group
receives:{ "type": "message", "from": "group", "group": "group", "dataType" : "binary", "data" : "AQID", // Base64-encoded [1,2,3] }
Because the JSON subprotocol client supports only text-based messaging, the binary is always Base64-encoded.
The simple WebSocket clients in
group
receive the binary data in the binary frame:# Show in hexadecimal 01 02 03
Send custom events
There's an implicit dataType
, which can be protobuf
, text
, or binary
, depending on the dataType
you set. The receiver clients can use dataType
to handle the content correctly.
protobuf
: When you setevent_message.data.protobuf_data
, the implicitdataType
isprotobuf
. Theprotobuf_data
value can be any supported protobuf type. The event handler receives the protobuf-encoded binary, which can be deserialized by any protobuf SDK.text
: When you setevent_message.data.text_data
, the implicitdataType
istext
. Thetext_data
value should be a string. The event handler receives a UTF-8-encoded string.binary
: When you setevent_message.data.binary_data
, the implicitdataType
isbinary
. Thebinary_data
value should be a byte array. The event handler receives the raw binary frame.
Case 1: Send an event with text data
Set event_message.data.text_data
to "text data"
.
The upstream event handler receives a request similar to:
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
The Content-Type
for the CloudEvents HTTP request is text/plain
, where dataType
=text
.
Case 2: Send an event with protobuf data
Assume that you've received the following customer message:
message MyMessage {
int32 value = 1;
}
Set event_message.data.protobuf_data
to any.pack(MyMessage)
with value = 1
The upstream event handler receives a request that's similar to:
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
The Content-Type
for the CloudEvents HTTP request is application/x-protobuf
, where dataType
=protobuf
.
The data is a valid protobuf binary. You can use the following proto
and any.unpack()
to deserialize it:
syntax = "proto3";
message MyMessage {
int32 value = 1;
}
Case 3: Send an event with binary data
Set send_to_group_message.binary_data
to [1, 2, 3]
.
The upstream event handler receives a request similar to:
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
For dataType
=binary
, the Content-Type
for the CloudEvents HTTP request is application/octet-stream
. The WebSocket frame can be in text
format for text message frames or UTF-8-encoded binaries for binary
message frames.
The service declines the client if the message doesn't match the prescribed format.
Ping
The client can send a PingMessage
to the service to enable the Web PubSub service to detect the client's liveness.
Responses
All response messages adhere to the following protobuf format:
message DownstreamMessage {
oneof message {
AckMessage ack_message = 1;
DataMessage data_message = 2;
SystemMessage system_message = 3;
PongMessage pong_message = 4;
}
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;
}
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 {
}
}
Messages received by the client can be in any of three types: ack
, message
, system
or pong
.
Ack response
If the request contains ackId
, the service returns an ack response for this request. The client implementation should handle this ack mechanism, including:
- Waiting for the ack response for an
async
await
operation. - Having a timeout check when the ack response isn't received during a certain period.
The client implementation should always check first to see whether the success
status is true
or false
. When the success
status is false
, the client can read from the error
property for error details.
Message response
Clients can receive messages published from a group that the client has joined. Or they can receive messages from the server management role when the server sends messages to a specific client or a specific user.
You'll always get a DownstreamMessage.DataMessage
message in the following scenarios:
- When the message is from a group,
from
isgroup
. When the message is from the server,from
isserver
. - When the message is from a group,
group
is the group name.
The sender's dataType
will cause one of the following messages to be sent:
- If
dataType
istext
, usemessage_response_message.data.text_data
. - If
dataType
isbinary
, usemessage_response_message.data.binary_data
. - If
dataType
isprotobuf
, usemessage_response_message.data.protobuf_data
. - If
dataType
isjson
, usemessage_response_message.data.text_data
, and the content is a serialized JSON string.
System response
The Web PubSub service can also send system-related responses to the client.
Connected
When the client connects to the service, you receive a DownstreamMessage.SystemMessage.ConnectedMessage
message.
Disconnected
When the server closes the connection or the service declines the client, you receive a DownstreamMessage.SystemMessage.DisconnectedMessage
message.
Pong response
The Web PubSub service sends a PongMessage
to the client when it receives a PingMessage
from the client.
Next steps
Use these resources to start building your own application: