使用 Azure SignalR 服务进行 Azure Functions 开发和配置Azure Functions development and configuration with Azure SignalR Service

Azure Functions 应用程序可以利用 Azure SignalR 服务绑定来添加实时功能。Azure Functions applications can leverage the Azure SignalR Service bindings to add real-time capabilities. 客户端应用程序使用以多种语言编写的客户端 SDK 连接到 Azure SignalR 服务和接收实时消息。Client applications use client SDKs available in several languages to connect to Azure SignalR Service and receive real-time messages.

本文介绍有关开发和配置与 SignalR 服务集成的 Azure 函数应用的概念。This article describes the concepts for developing and configuring an Azure Function app that is integrated with SignalR Service.

SignalR 服务配置SignalR Service configuration

可以不同的模式配置 Azure SignalR 服务。Azure SignalR Service can be configured in different modes. 与 Azure Functions 一起使用时,必须以“无服务器”模式配置服务。When used with Azure Functions, the service must be configured in Serverless mode.

在 Azure 门户中,找到 SignalR 服务资源的“设置”页。In the Azure portal, locate the Settings page of your SignalR Service resource. 将“服务模式”设置为“无服务器”。 Set the Service mode to Serverless.

SignalR 服务模式

Azure Functions 开发Azure Functions development

使用 Azure Functions 和 Azure SignalR 服务构建的无服务器实时应用程序通常需要两个 Azure 函数:A serverless real-time application built with Azure Functions and Azure SignalR Service typically requires two Azure Functions:

  • “negotiate”函数:客户端调用该函数来获取有效的 SignalR 服务访问令牌和服务终结点 URLA "negotiate" function that the client calls to obtain a valid SignalR Service access token and service endpoint URL
  • 一个或多个用于处理来自 SignalR 服务的消息、发送消息或管理组成员身份的函数One or more functions that handle messages from SignalR Service and send messages or manage group membership

negotiate 函数negotiate function

客户端应用程序需要获取有效的访问令牌才能连接到 Azure SignalR 服务。A client application requires a valid access token to connect to Azure SignalR Service. 访问令牌可以是匿名的,也可以是经过身份验证的给定用户 ID。An access token can be anonymous or authenticated to a given user ID. 无服务器 SignalR 服务应用程序需要使用名为“negotiate”的 HTTP 终结点来获取令牌和其他连接信息,例如 SignalR 服务终结点 URL。Serverless SignalR Service applications require an HTTP endpoint named "negotiate" to obtain a token and other connection information, such as the SignalR Service endpoint URL.

使用 HTTP 触发的 Azure 函数和 SignalRConnectionInfo 输入绑定生成连接信息对象。Use an HTTP triggered Azure Function and the SignalRConnectionInfo input binding to generate the connection information object. 该函数必须包含以 /negotiate 结尾的 HTTP 路由。The function must have an HTTP route that ends in /negotiate.

通过使用 C# 中基于类的模型,可无需执行 SignalRConnectionInfo 输入绑定,并可以轻松得多的方式添加自定义声明。With class based model in C#, you don't need SignalRConnectionInfo input binding and can add custom claims much easier. 请参阅基于类的模型中的协商体验See Negotiate experience in class based model

有关如何创建 negotiate 函数的详细信息,请参阅 SignalRConnectionInfo 输入绑定参考For more information on how to create the negotiate function, see the SignalRConnectionInfo input binding reference.

若要了解如何创建经过身份验证的令牌,请参阅使用应用服务身份验证To learn about how to create an authenticated token, refer to Using App Service Authentication.

处理从 SignalR 服务发送的消息Handle messages sent from SignalR Service

使用 SignalR 触发器绑定来处理从 SignalR 服务发送的消息。Use the SignalR Trigger binding to handle messages sent from SignalR Service. 可在客户端发送消息或客户端连接或断开连接时通过触发功能收到通知。You can be triggered when clients send messages or clients get connected or disconnected.

有关详细信息,请参阅 SignalR 触发器绑定参考For more information, see the SignalR trigger binding reference.

还需要将函数终结点配置为上游,让服务在收到来自客户端的消息时触发函数。You also need to configure your function endpoint as an upstream so that service will trigger the function where there is message from client. 有关如何配置上游的详细信息,请参阅此文档For more information about how to configure upstream, please refer to this doc.

发送消息和管理组成员身份Sending messages and managing group membership

使用 SignalR 输出绑定将消息发送到与 Azure SignalR 服务连接的客户端。Use the SignalR output binding to send messages to clients connected to Azure SignalR Service. 可将消息广播到所有客户端,或者将其发送到已使用特定用户 ID 进行身份验证的或已添加到特定组的一部分客户端。You can broadcast messages to all clients, or you can send them to a subset of clients that are authenticated with a specific user ID or have been added to a specific group.

可将用户添加到一个或多个组。Users can be added to one or more groups. 还可以使用 SignalR 输出绑定在组中添加或删除用户。You can also use the SignalR output binding to add or remove users to/from groups.

有关详细信息,请参阅 SignalR 输出绑定参考For more information, see the SignalR output binding reference.

SignalR 中心SignalR Hubs

SignalR 具有“中心”的概念。SignalR has a concept of "hubs". 每个客户端连接以及从 Azure Functions 发送的每个消息的范围限定为特定的中心。Each client connection and each message sent from Azure Functions is scoped to a specific hub. 可以使用中心将连接和消息划分到逻辑命名空间。You can use hubs as a way to separate your connections and messages into logical namespaces.

基于类的模型Class based model

基于类的模型专用于 C#。The class based model is dedicated for C#. 使用基于类的模型可以拥有一致的 SignalR 服务器端编程体验。With class based model can have a consistent SignalR server-side programming experience. 该示例应用程序具有以下功能。It has the following features.

  • 更少的配置工作:类名称用作 HubName,方法名称用作 EventCategory 根据方法名称自动确定。Less configuration work: The class name is used as HubName, the method name is used as Event and the Category is decided automatically according to method name.
  • 自动参数绑定:不需要 ParameterNames 和属性 [SignalRParameter]Auto parameter binding: Neither ParameterNames nor attribute [SignalRParameter] is needed. 参数按顺序自动绑定到 Azure Function 方法的参数上。Parameters are auto bound to arguments of Azure Function method in order.
  • 方便的输出和协商体验。Convenient output and negotiate experience.

以下代码演示了这些功能:The following codes demonstrate these features:

public class SignalRTestHub : ServerlessHub
{
    [FunctionName("negotiate")]
    public SignalRConnectionInfo Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req)
    {
        return Negotiate(req.Headers["x-ms-signalr-user-id"], GetClaims(req.Headers["Authorization"]));
    }

    [FunctionName(nameof(OnConnected))]
    public async Task OnConnected([SignalRTrigger]InvocationContext invocationContext, ILogger logger)
    {
        await Clients.All.SendAsync(NewConnectionTarget, new NewConnection(invocationContext.ConnectionId));
        logger.LogInformation($"{invocationContext.ConnectionId} has connected");
    }

    [FunctionName(nameof(Broadcast))]
    public async Task Broadcast([SignalRTrigger]InvocationContext invocationContext, string message, ILogger logger)
    {
        await Clients.All.SendAsync(NewMessageTarget, new NewMessage(invocationContext, message));
        logger.LogInformation($"{invocationContext.ConnectionId} broadcast {message}");
    }

    [FunctionName(nameof(OnDisconnected))]
    public void OnDisconnected([SignalRTrigger]InvocationContext invocationContext)
    {
    }
}

需要利用基于类的模型的所有函数都需是继承自 ServerlessHub 的类的方法。All functions that want to leverage class based model need to be the method of class that inherits from ServerlessHub. 示例中的类名称 SignalRTestHub 是中心名称。The class name SignalRTestHub in the sample is the hub name.

定义中心方法Define hub method

所有中心方法必须具有由 [SignalRTrigger] 属性修饰的 InvocationContext 参数,并使用无参数构造函数。All the hub methods must have an argument of InvocationContext decorated by [SignalRTrigger] attribute and use parameterless constructor. 然后将方法名称视为参数 event 。Then the method name is treated as parameter event.

默认情况下,除了方法名称外,category=messages 是以下名称之一:By default, category=messages except the method name is one of the following names:

  • OnConnected:视为 category=connections, event=connectedOnConnected: Treated as category=connections, event=connected
  • OnDisconnected:视为 category=connections, event=disconnectedOnDisconnected: Treated as category=connections, event=disconnected

参数绑定体验Parameter binding experience

在基于类的模型中,[SignalRParameter] 是不必要的,因为默认情况下,所有参数都标记为 [SignalRParameter],除非是以下情况之一:In class based model, [SignalRParameter] is unnecessary because all the arguments are marked as [SignalRParameter] by default except it is one of the following situations:

  • 参数由绑定属性修饰。The argument is decorated by a binding attribute.
  • 参数的类型为 ILoggerCancellationTokenThe argument's type is ILogger or CancellationToken
  • 参数由属性 [SignalRIgnore] 修饰The argument is decorated by attribute [SignalRIgnore]

基于类的模型中的协商体验Negotiate experience in class based model

与使用 SignalR 输入绑定 [SignalR] 相比,基于类的模型中的协商更为灵活。Instead of using SignalR input binding [SignalR], negotiation in class based model can be more flexible. 基类 ServerlessHub 具有一个方法Base class ServerlessHub has a method

SignalRConnectionInfo Negotiate(string userId = null, IList<Claim> claims = null, TimeSpan? lifeTime = null)

此功能使用户可以在函数执行期间自定义 userIdclaimsThis features user customizes userId or claims during the function execution.

使用 SignalRFilterAttributeUse SignalRFilterAttribute

用户可以继承和实现抽象类 SignalRFilterAttributeUser can inherit and implement the abstract class SignalRFilterAttribute. 如果 FilterAsync 中引发异常,会将 403 Forbidden 发送回客户端。If exceptions are thrown in FilterAsync, 403 Forbidden will be sent back to clients.

下面的示例演示如何实现仅允许 admin 调用 broadcast 的客户筛选器。The following sample demonstrates how to implement a customer filter that only allows the admin to invoke broadcast.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
internal class FunctionAuthorizeAttribute: SignalRFilterAttribute
{
    private const string AdminKey = "admin";

    public override Task FilterAsync(InvocationContext invocationContext, CancellationToken cancellationToken)
    {
        if (invocationContext.Claims.TryGetValue(AdminKey, out var value) &&
            bool.TryParse(value, out var isAdmin) &&
            isAdmin)
        {
            return Task.CompletedTask;
        }

        throw new Exception($"{invocationContext.ConnectionId} doesn't have admin role");
    }
}

利用属性对函数进行授权。Leverage the attribute to authorize the function.

[FunctionAuthorize]
[FunctionName(nameof(Broadcast))]
public async Task Broadcast([SignalRTrigger]InvocationContext invocationContext, string message, ILogger logger)
{
}

客户端开发Client development

SignalR 客户端应用程序可利用以多种语言之一编写的 SignalR 客户端 SDK 轻松连接到 Azure SignalR 服务并从中接收消息。SignalR client applications can leverage the SignalR client SDK in one of several languages to easily connect to and receive messages from Azure SignalR Service.

配置客户端连接Configuring a client connection

若要连接到 SignalR 服务,客户端必须成功完成连接协商,具体包括以下步骤:To connect to SignalR Service, a client must complete a successful connection negotiation that consists of these steps:

  1. 向上述 HTTP 协商终结点发出请求,以获取有效的连接信息Make a request to the negotiate HTTP endpoint discussed above to obtain valid connection information
  2. 使用服务终结点 URL 以及从协商终结点获取的访问令牌连接到 SignalR 服务Connect to SignalR Service using the service endpoint URL and access token obtained from the negotiate endpoint

SignalR 客户端 SDK 已包含执行协商握手所需的逻辑。SignalR client SDKs already contain the logic required to perform the negotiation handshake. 将协商终结点的 URL(不包括 negotiate 段)传递给 SDK 的 HubConnectionBuilderPass the negotiate endpoint's URL, minus the negotiate segment, to the SDK's HubConnectionBuilder. 下面是一个 JavaScript 示例:Here is an example in JavaScript:

const connection = new signalR.HubConnectionBuilder()
  .withUrl('https://my-signalr-function-app.chinacloudsites.cn/api')
  .build()

SDK 根据约定自动将 /negotiate 追加到 URL,然后使用该 URL 开始协商。By convention, the SDK automatically appends /negotiate to the URL and uses it to begin the negotiation.

备注

如果在浏览器中使用 JavaScript/TypeScript SDK,则需要在函数应用中启用跨源资源共享 (CORS)If you are using the JavaScript/TypeScript SDK in a browser, you need to enable cross-origin resource sharing (CORS) on your Function App.

有关如何使用 SignalR 客户端 SDK 的详细信息,请参阅适用于所用语言的文档:For more information on how to use the SignalR client SDK, refer to the documentation for your language:

将消息从客户端发送到服务Sending messages from a client to the service

如果为 SignalR 资源配置了上游,则可以使用任何 SignalR 客户端将消息从客户端发送到 Azure Functions。If you have upstream configured for your SignalR resource, you can send messages from client to your Azure Functions using any SignalR client. 下面是一个 JavaScript 示例:Here is an example in JavaScript:

connection.send('method1', 'arg1', 'arg2');

Azure Functions 配置Azure Functions configuration

可以使用 zip 部署从包运行等技术,像部署任何典型 Azure 函数应用一样,来部署与 Azure SignalR 服务集成的 Azure 函数应用。Azure Function apps that integrate with Azure SignalR Service can be deployed like any typical Azure Function app, using techniques such as zip deployment, and run from package.

但是,对于使用 SignalR 服务绑定的应用,需要注意几个特殊事项。However, there are a couple of special considerations for apps that use the SignalR Service bindings. 如果客户端在浏览器中运行,则必须启用 CORS。If the client runs in a browser, CORS must be enabled. 如果应用需要身份验证,则你可以将协商终结点与应用服务身份验证集成。And if the app requires authentication, you can integrate the negotiate endpoint with App Service Authentication.

启用 CORSEnabling CORS

JavaScript/TypeScript 客户端向 negotiate 函数发出 HTTP 请求,以启动连接协商。The JavaScript/TypeScript client makes HTTP requests to the negotiate function to initiate the connection negotiation. 如果客户端应用程序不是托管在 Azure 函数应用所在的同一个域中,则必须在函数应用中启用跨源资源共享 (CORS),否则浏览器会阻止请求。When the client application is hosted on a different domain than the Azure Function app, cross-origin resource sharing (CORS) must be enabled on the Function app or the browser will block the requests.

LocalhostLocalhost

在本地计算机上运行函数应用时,可将 Host 节添加到 local.settings.json 以启用 CORS。When running the Function app on your local computer, you can add a Host section to local.settings.json to enable CORS. Host 节中添加两个属性:In the Host section, add two properties:

  • CORS - 输入作为客户端应用程序来源的基本 URLCORS - enter the base URL that is the origin the client application
  • CORSCredentials - 将其设置为 true 以允许“withCredentials”请求CORSCredentials - set it to true to allow "withCredentials" requests

示例:Example:

{
  "IsEncrypted": false,
  "Values": {
    // values
  },
  "Host": {
    "CORS": "http://localhost:8080",
    "CORSCredentials": true
  }
}

云 - Azure Functions CORSCloud - Azure Functions CORS

若要在 Azure 函数应用中启用 CORS,请在 Azure 门户中函数应用的“平台功能”选项卡下,转到 CORS 配置屏幕。To enable CORS on an Azure Function app, go to the CORS configuration screen under the Platform features tab of your Function app in the Azure portal.

必须启用支持 Access-Control-Allow-Credentials 的 CORS 才能让 SignalR 客户端调用 negotiate 函数。CORS with Access-Control-Allow-Credentials must be enabled for the SignalR client to call the negotiate function. 选中相应的复选框以启用 CORS。Select the checkbox to enable it.

在“允许的源”部分添加一个包含 Web 应用程序源基本 URL 的条目。In the Allowed origins section, add an entry with the origin base URL of your web application.

配置 CORS

云 - Azure API 管理Cloud - Azure API Management

Azure API 管理提供一个可向现有后端服务添加功能的 API 网关。Azure API Management provides an API gateway that adds capabilities to existing back-end services. 可以使用 API 管理将 CORS 添加到函数应用。You can use it to add CORS to your function app. 它提供按操作定价并授予每月免费额度的消耗层。It offers a consumption tier with pay-per-action pricing and a monthly free grant.

请参阅 API 管理文档来了解如何导入 Azure 函数应用Refer to the API Management documentation for information on how to import an Azure Function app. 导入后,可以添加一个入站策略来启用支持 Access-Control-Allow-Credentials 的 CORS。Once imported, you can add an inbound policy to enable CORS with Access-Control-Allow-Credentials support.

<cors allow-credentials="true">
  <allowed-origins>
    <origin>https://azure-samples.github.io</origin>
  </allowed-origins>
  <allowed-methods>
    <method>GET</method>
    <method>POST</method>
  </allowed-methods>
  <allowed-headers>
    <header>*</header>
  </allowed-headers>
  <expose-headers>
    <header>*</header>
  </expose-headers>
</cors>

将 SignalR 客户端配置为使用 API 管理 URL。Configure your SignalR clients to use the API Management URL.

使用应用服务身份验证Using App Service Authentication

Azure Functions 提供内置的身份验证,支持 Microsoft 帐户和 Azure Active Directory 等流行提供程序。Azure Functions has built-in authentication, supporting popular providers such as Microsoft Account and Azure Active Directory. 此功能可与 SignalRConnectionInfo 绑定集成,以便与已使用用户 ID 进行身份验证的 Azure SignalR 服务建立连接。This feature can be integrated with the SignalRConnectionInfo binding to create connections to Azure SignalR Service that have been authenticated to a user ID. 应用程序可以使用 SignalR 输出绑定来发送以该用户 ID 为目标的消息。Your application can send messages using the SignalR output binding that are targeted to that user ID.

在 Azure 门户中函数应用的“平台功能”选项卡上,打开“身份验证/授权”设置窗口。 In the Azure portal, in your Function app's Platform features tab, open the Authentication/authorization settings window. 遵循应用服务身份验证文档使用所选的标识提供者配置身份验证。Follow the documentation for App Service Authentication to configure authentication using an identity provider of your choice.

配置后,经过身份验证的 HTTP 请求将包含 x-ms-client-principal-namex-ms-client-principal-id 标头,而这些标头分别包含经过身份验证的标识的用户名和用户 ID。Once configured, authenticated HTTP requests will include x-ms-client-principal-name and x-ms-client-principal-id headers containing the authenticated identity's username and user ID, respectively.

可以在 SignalRConnectionInfo 绑定配置中使用这些标头来创建经过身份验证的连接。You can use these headers in your SignalRConnectionInfo binding configuration to create authenticated connections. 下面是一个使用 x-ms-client-principal-id 标头的 C# negotiate 函数示例。Here is an example C# negotiate function that uses the x-ms-client-principal-id header.

[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
    [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
    [SignalRConnectionInfo
        (HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
        SignalRConnectionInfo connectionInfo)
{
    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
    return connectionInfo;
}

然后,可以通过设置 SignalR 消息的 UserId 属性向该用户发送消息。You can then send messages to that user by setting the UserId property of a SignalR message.

[FunctionName("SendMessage")]
public static Task SendMessage(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
    [SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    return signalRMessages.AddAsync(
        new SignalRMessage
        {
            // the message will only be sent to these user IDs
            UserId = "userId1",
            Target = "newMessage",
            Arguments = new [] { message }
        });
}

有关其他语言的信息,请参阅 Azure Functions 参考文章 Azure SignalR 服务绑定For information on other languages, see the Azure SignalR Service bindings for Azure Functions reference.

后续步骤Next steps

本文介绍了如何使用 Azure Functions 开发和配置无服务器 SignalR 服务应用程序。In this article, you have learned how to develop and configure serverless SignalR Service applications using Azure Functions. 请尝试使用 SignalR 服务概述页上的某篇快速入门或教程自行创建应用程序。Try creating an application yourself using one of the quick starts or tutorials on the SignalR Service overview page.