Compartilhar via

用于Azure Web PubSub的 WebSocket 客户端协议

客户端使用标准 WebSocket 协议连接到Azure Web PubSub。

服务终结点

Web PubSub 服务提供两种类型的终结点供客户端与之连接:

  • /client/hubs/{hub}
  • /client/?hub={hub}

{hub} 是必需参数,充当各种应用程序的隔离项。 可以在路径或查询中设置它。

授权

客户端使用 JSON Web 令牌 (JWT) 连接到服务。 可以将令牌以 /client/?hub={hub}&access_token={token} 形式置于查询字符串中,或以 Authorization 形式置于 Authorization: Bearer {token} 标头中。

下面是常规授权工作流:

  1. 客户端与应用程序服务器协商。 应用程序服务器包含的授权中间件可处理客户端请求,并对 JWT 进行签名,以使客户端连接到服务。
  2. 应用程序服务器将 JWT 和服务 URL 返回到客户端。
  3. 客户端尝试使用 URL 和从应用程序服务器返回的 JWT 连接到 Web PubSub 服务。

支持的声明

还可以通过在 JWT 中指定特殊声明,在生成访问令牌时配置客户端连接的属性:

说明 声明类型 声明值 备注
用于客户端连接的 userId sub 用户ID (userId) 只允许一个 sub 声明。
令牌的生存期 exp 过期时间 exp(过期时间)声明标识了一个时间,在该时间或之后,令牌不应被接受处理。
客户端连接最初具有的权限 role 权限中定义的角色值 如果客户端具有多个权限,请指定多个 role 声明。
连接到 Azure Web PubSub 后客户端连接加入的初始组 webpubsub.group 要加入的群组 如果客户端加入多个组,请指定多个 webpubsub.group 声明。

还可以将自定义声明添加到访问令牌中,这些值将保留为连接上游请求体中的claims属性。

服务器 SDK 提供用于为客户端生成访问令牌的 API。

简单的 WebSocket 客户端

顾名思义,简单的 WebSocket 客户端就是一个简单的 WebSocket 连接。 它还可以有自己的自定义子协议。

例如,在 JavaScript 中,可以使用以下代码创建简单的 WebSocket 客户端:

// simple WebSocket client1
var client1 = new WebSocket('wss://test.webpubsub.azure.cn/client/hubs/hub1');

// simple WebSocket client2 with some custom subprotocol
var client2 = new WebSocket('wss://test.webpubsub.azure.cn/client/hubs/hub1', 'custom.subprotocol')

简单 WebSocket 客户端有两种模式:sendEventsendToGroup。 一旦建立连接,模式就确定了,之后无法更改。

sendEvent 是简单 WebSocket 客户端的默认模式。 在 sendEvent 模式下,客户端发送的每个 WebSocket 帧都被视为 message 事件。 用户可以配置事件处理程序事件侦听器来处理这些 message 事件。

// Every data frame is considered as a `message` event
var client3 = new WebSocket('wss://test.webpubsub.azure.cn/client/hubs/hub1');

// Or explicitly set the mode
var client4 = new WebSocket('wss://test.webpubsub.azure.cn/client/hubs/hub1?webpubsub_mode=sendEvent');

sendToGroup 模式下,客户端发送的每个 WebSocket 帧都被视为一条要发布到特定组的消息。 group 在该模式下是必需的查询参数,只允许单个值。 该连接还应具有相应的权限,以便向目标组发送消息。 webpubsub.sendToGroup.<group>webpubsub.sendToGroup 角色都适用于它。

例如,在 JavaScript 中,可以使用以下代码创建一个采用 sendToGroup 模式且 group=group1 的简单 WebSocket 客户端:

var client5 = new WebSocket('wss://test.webpubsub.azure.cn/client/hubs/hub1?webpubsub_mode=sendToGroup&group=group1');

PubSub WebSocket 客户端

PubSub WebSocket 客户端是使用 Azure Web PubSub 服务定义的子协议的 WebSocket 客户端:

  • json.webpubsub.azure.v1
  • protobuf.webpubsub.azure.v1

通过使用服务支持的子协议,PubSub WebSocket 客户端在有相关权限时可以直接将消息发布到组。

json.webpubsub.azure.v1 子协议

查看此处详细了解 JSON 子协议

创建 PubSub WebSocket 客户端

var pubsubClient = new WebSocket('wss://test.webpubsub.azure.cn/client/hubs/hub1', 'json.webpubsub.azure.v1');

直接从客户端加入组

let ackId = 0;
pubsubClient.send(
    JSON.stringify({
        type: 'joinGroup',
        group: 'group1',
        ackId: ++ackId
    }));

直接从客户端向组发送消息

let ackId = 0;
pubsubClient.send(
    JSON.stringify({
        type: 'sendToGroup',
        group: 'group1',
        ackId: ++ackId,
        dataType: "json",
        data: {
            "hello": "world"
        }
    }));

protobuf.webpubsub.azure.v1 子协议

协议缓冲区 (protobuf) 是一种语言中性、平台中性的基于二进制的协议,可简化二进制数据的发送。 Protobuf 提供了为多种语言(例如Java、Python、Objective-C、C# 和 C++)生成客户端的工具。

例如,在 JavaScript 中,可以通过以下代码创建使用 protobuf 子协议的 PubSub WebSocket 客户端:

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

在这里详细了解 protobuf 子协议的详细信息

AckId 和 Ack 响应

PubSub WebSocket 客户端支持消息的 属性,包括 ackIdjoinGroupleaveGroupsendToGroup。 当你使用 ackId 时,你的请求被处理后可以收到确认响应消息。 可以在即发即弃场景中选择省略 ackId。 本文介绍了指定或不指定 ackId 时的行为差异。

未指定 ackId 时的行为

如果未指定 ackId,则是即发即弃。 即使中转消息时出现错误,你也无法收到通知。

指定 ackId 时的行为

幂等发布

ackId 是 uint64 数字,在具有相同连接 ID 的客户端中应是唯一的。 Web PubSub 服务会记录 ackId,具有相同 ackId 的消息将被视为同一消息。 服务拒绝多次中转同一消息,这有助于重试时避免重复的消息。 例如,如果客户端发送 ackId=5 的消息并且无法接收 ackId=5 的确认响应,那么客户端将再次重试并发送相同的消息。 在某些情况下,消息已经被中转,而确认响应由于某种原因丢失了。 服务会拒绝重试,并以Duplicate原因发送确认响应。

确认响应

Web PubSub 服务将为具有 ackId 的每个请求发送确认响应。

格式:

{
    "type": "ack",
    "ackId": 1, // The ack id for the request to ack
    "success": false, // true or false
    "error": {
        "name": "Forbidden|InternalServerError|Duplicate",
        "message": "<error_detail>"
    }
}
  • ackId 与请求相关联。

  • success 是一个布尔值,指示服务是否成功处理了请求。 如果为 false,客户端需要检查 error

  • error 仅在 successfalse 时存在,并且客户端针对不同的 name 应具有不同的逻辑。 你应假设将来可能有更多类型的 name

    • Forbidden:客户端没有请求权限。 需要向客户端添加相关角色。
    • InternalServerError:服务中出现了一些内部错误。 需要重试。
    • Duplicate:该服务已经处理了具有相同ackId的消息。

权限

你可能已在前面的 PubSub WebSocket 客户端介绍中注意到,一个客户端只有在获得授权时才能发布到其他客户端。 可以在客户端连接时或在连接的持续期间授予其权限。

角色 许可
未指定 客户端可以发送事件请求。
webpubsub.joinLeaveGroup 客户端可以加入或退出任何组。
webpubsub.sendToGroup 客户端可以向任何组发布消息。
webpubsub.joinLeaveGroup.<group> 客户端可以加入或退出 <group> 组。
webpubsub.sendToGroup.<group> 客户端可以向 <group> 组发布消息。

可以通过多种方式授予客户端的权限:

1. 生成访问令牌时将角色分配给客户端

客户端可以使用 JWT 连接到服务。 令牌的有效负载可以携带例如客户端 role 的信息。 将 JWT 签名到客户端时,可以通过授予客户端特定角色来向客户端授予权限。

例如,我们对有权限向 group1group2 发送消息的 JWT 进行签名:

let token = await serviceClient.getClientAccessToken({
    roles: [ "webpubsub.sendToGroup.group1", "webpubsub.sendToGroup.group2" ]
});

2. 使用 connect 事件处理程序将角色分配给客户端

还可以在注册 connect 事件处理程序时设置客户端的角色,并且上游事件处理程序可以在处理 roles 事件时将客户端的 connect 返回给 Web PubSub 服务。

例如,在 JavaScript 中,可以配置 handleConnect 事件来这样做:

let handler = new WebPubSubEventHandler("hub1", {
  handleConnect: (req, res) => {
    // auth the connection and set the userId of the connection
    res.success({
      roles: [ "webpubsub.sendToGroup.group1", "webpubsub.sendToGroup.group2" ]
    });
  },
});

3. 在运行时期间通过 REST API 或服务器 SDK 将角色分配给客户端

let service = new WebPubSubServiceClient("<your_connection_string>", "test-hub");
await service.grantPermission("<connection_id>", "joinLeaveGroup", { targetName: "group1" });

注释

尚不支持通过 REST API 或服务器 SDK 分配通配符角色(例如 webpubsub.sendToGroups.<pattern>)。 将来的更新将支持此功能。

后续步骤

使用这些资源开始生成自己的应用程序: