Azure Functions 的 SignalR 服务输入绑定

客户端在连接到 Azure SignalR 服务之前,必须检索服务终结点 URL 和有效的访问令牌。 SignalRConnectionInfo 输入绑定生成 SignalR 服务终结点 URL 和有效的令牌,这两者可以用来连接到服务。 此令牌有时间限制,并且可用来对需要连接的特定用户进行身份验证。 因此,请勿缓存令牌或在客户端之间共享令牌。 通常,将 SignalRConnectionInfo 与 HTTP 触发器配合使用,以便客户端检索连接信息。

有关如何使用此绑定来创建一个与 SignalR 客户端 SDK 兼容的“协商”函数的详细信息,请参阅使用 Azure SignalR 服务进行 Azure Functions 开发和配置一文。 若要了解设置和配置详细信息,请参阅概述

示例

可使用以下 C# 模式之一来创建 C# 函数:

  • 进程内类库:编译的 C# 函数,该函数在与 Functions 运行时相同的进程中运行。
  • 独立工作进程类库:编译的 C# 函数,该函数在独立于运行时的工作进程中运行。 需要独立工作进程才能支持在 LTS 和非 LTS 版 .NET 和 .NET Framework 上运行的 C# 函数。
  • C# 脚本:主要在 Azure 门户中创建 C# 函数时使用。

以下示例演示了一个 C# 函数,该函数使用输入绑定获取 SignalR 连接信息,并通过 HTTP 将其返回。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

namespace Extensions.SignalR
{
    public static class SignalRNegotiationFunctions
    {
        // <snippet_negotiate>
        [Function(nameof(Negotiate))]
        public static string Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
            [SignalRConnectionInfoInput(HubName = "serverless")] string connectionInfo)
        {
            // The serialization of the connection info object is done by the framework. It should be camel case. The SignalR client respects the camel case response only.
            return connectionInfo;
        }
        // </snippet_negotiate>


        // When you have multiple SignalR service instances and you want to customize the rule that route a client
        // <snippet_negotiate_multiple_endpoint>
        public static readonly JsonSerializerOptions SerializerOptions = new()
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        [Function(nameof(NegotiateWithMultipleEndpoints))]
        public static string NegotiateWithMultipleEndpoints(
            [HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
            [SignalRNegotiationInput("chatHub", "SignalRConnection")] SignalRNegotiationContext negotiationContext)
        {
            // Customize your rule here
            var connectionInfo = negotiationContext.Endpoints[0].ConnectionInfo;
            // The SignalR client respects the camel case response only.
            return JsonSerializer.Serialize(connectionInfo, SerializerOptions);
        }
        // </snippet_negotiate_multiple_endpoint>

    }
}

以下示例演示了 function.json 文件中的一个 SignalR 连接信息输入绑定,以及使用该绑定来返回连接信息的函数。

下面是 function.json 文件中的绑定数据:

{
    "type": "signalRConnectionInfo",
    "name": "connectionInfo",
    "hubName": "chat",
    "connectionStringSetting": "<name of setting containing SignalR Service connection string>",
    "direction": "in"
}

JavaScript 代码如下所示:

module.exports = async function (context, req, connectionInfo) {
    context.res.body = connectionInfo;
};

完整的 PowerShell 示例处于待定状态。

以下示例演示了 function.json 文件中的一个 SignalR 连接信息输入绑定,以及使用该绑定来返回连接信息的 Python 函数

下面是 Python 代码:

def main(req: func.HttpRequest, connectionInfoJson: str) -> func.HttpResponse:
    return func.HttpResponse(
        connectionInfoJson,
        status_code=200,
        headers={
            'Content-type': 'application/json'
        }
    )

以下示例演示了一个 Java 函数,该函数使用输入绑定获取 SignalR 连接信息,并通过 HTTP 将其返回。

@FunctionName("negotiate")
public SignalRConnectionInfo negotiate(
        @HttpTrigger(
            name = "req",
            methods = { HttpMethod.POST },
            authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> req,
        @SignalRConnectionInfoInput(
            name = "connectionInfo",
            hubName = "chat") SignalRConnectionInfo connectionInfo) {
    return connectionInfo;
}

使用情况

已进行身份验证的令牌

当此函数由经过身份验证的客户端触发时,可向生成的令牌添加用户 ID 声明。 可以轻松地使用应用服务身份验证向函数应用添加身份验证。

应用服务身份验证会设置名为 x-ms-client-principal-idx-ms-client-principal-name(分别包含经身份验证的用户的客户端主体 ID 和名称)的 HTTP 标头。

[Function("Negotiate")]
public static string Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
    [SignalRConnectionInfoInput(HubName = "serverless", UserId = "{headers.x-ms-client-principal-id}")] string connectionInfo)
{
    // The serialization of the connection info object is done by the framework. It should be camel case. The SignalR client respects the camel case response only.
    return connectionInfo;
}
@FunctionName("negotiate")
public SignalRConnectionInfo negotiate(
        @HttpTrigger(
            name = "req",
            methods = { HttpMethod.POST, HttpMethod.GET },
            authLevel = AuthorizationLevel.ANONYMOUS)
            HttpRequestMessage<Optional<String>> req,
        @SignalRConnectionInfoInput(name = "connectionInfo", hubName = "simplechat", userId = "{headers.x-ms-signalr-userid}") SignalRConnectionInfo connectionInfo) {
    return connectionInfo;
}

可以使用绑定表达式{headers.x-ms-client-principal-id}{headers.x-ms-client-principal-name} 将绑定的 userId 属性设置为任一标头中的值。

下面是 function.json 文件中的绑定数据:

{
    "type": "signalRConnectionInfo",
    "name": "connectionInfo",
    "hubName": "chat",
    "userId": "{headers.x-ms-client-principal-id}",
    "connectionStringSetting": "<name of setting containing SignalR Service connection string>",
    "direction": "in"
}

JavaScript 代码如下所示:

module.exports = async function (context, req, connectionInfo) {
    // connectionInfo contains an access key token with a name identifier
    // claim set to the authenticated user
    context.res.body = connectionInfo;
};

完整的 PowerShell 示例处于待定状态。

下面是 Python 代码:

def main(req: func.HttpRequest, connectionInfo: str) -> func.HttpResponse:
    # connectionInfo contains an access key token with a name identifier
    # claim set to the authenticated user
    return func.HttpResponse(
        connectionInfo,
        status_code=200,
        headers={
            'Content-type': 'application/json'
        }
    )

可以使用绑定表达式{headers.x-ms-client-principal-id}{headers.x-ms-client-principal-name} 将绑定的 userId 属性设置为任一标头中的值。

@FunctionName("negotiate")
public SignalRConnectionInfo negotiate(
        @HttpTrigger(
            name = "req",
            methods = { HttpMethod.POST },
            authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> req,
        @SignalRConnectionInfoInput(
            name = "connectionInfo",
            hubName = "chat",
            userId = "{headers.x-ms-client-principal-id}") SignalRConnectionInfo connectionInfo) {
    return connectionInfo;
}

特性

进程内独立工作进程 C# 库都使用特性来定义函数。 C# 脚本改为使用 function.json 配置文件。

下表说明了 SignalRConnectionInfoInput 特性的属性:

Attribute 属性 说明
HubName 必需。 中心名称。
ConnectionStringSetting 包含 SignalR 服务连接字符串(默认为 AzureSignalRConnectionString)的应用设置的名称。
UserId 可选。 SignalR 连接的用户标识符。 可以使用绑定表达式将值绑定到 HTTP 请求头或查询。
IdToken 可选。 一个 JWT 令牌,其声明将添加到用户声明中。 应与 ClaimTypeList 一起使用。 可以使用绑定表达式将值绑定到 HTTP 请求头或查询。
ClaimTypeList 可选。 声明类型的列表,用于筛选 IdToken 中的声明。

批注

下表说明了 SignalRConnectionInfoInput 批注的受支持的设置。

设置 说明
name 变量名称,在连接信息对象的函数代码中使用。
hubName 必需。 中心名称。
connectionStringSetting 包含 SignalR 服务连接字符串(默认为 AzureSignalRConnectionString)的应用设置的名称。
userId 可选。 SignalR 连接的用户标识符。 可以使用绑定表达式将值绑定到 HTTP 请求头或查询。
idToken 可选。 一个 JWT 令牌,其声明将添加到用户声明中。 应与 claimTypeList 一起使用。 可以使用绑定表达式将值绑定到 HTTP 请求头或查询。
claimTypeList 可选。 声明类型的列表,用于筛选 idToken 中的声明。

配置

下表解释了在 function.json 文件中设置的绑定配置属性。

“function.json”属性 说明
type 必须设置为 signalRConnectionInfo
direction 必须设置为 in
hubName 必需。 中心名称。
connectionStringSetting 包含 SignalR 服务连接字符串(默认为 AzureSignalRConnectionString)的应用设置的名称。
userId 可选。 SignalR 连接的用户标识符。 可以使用绑定表达式将值绑定到 HTTP 请求头或查询。
idToken 可选。 一个 JWT 令牌,其声明将添加到用户声明中。 应与 claimTypeList 一起使用。 可以使用绑定表达式将值绑定到 HTTP 请求头或查询。
claimTypeList 可选。 声明类型的列表,用于筛选 idToken 中的声明。

HTTP 触发器的绑定表达式

SignalR 输入绑定的某些属性的值通常来自 HTTP 请求。 因此,我们将演示如何通过绑定表达式将 HTTP 请求中的值绑定到 SignalR 输入绑定属性。

HTTP 元数据类型 绑定表达式格式 说明 示例
HTTP 请求查询 {query.QUERY_PARAMETER_NAME} 将相应查询参数的值绑定到属性 {query.userName}
HTTP 请求标头 {headers.HEADER_NAME} 将标头的值绑定到属性 {headers.token}

后续步骤