快速入门:使用 JavaScript 通过 Azure Functions 和 SignalR 服务创建无服务器应用

在本文中,你将使用 Azure SignalR 服务、Azure Functions 和 JavaScript 生成一个无服务器应用程序,以便向客户端广播消息。

先决条件

本快速入门可以在 macOS、Windows 或 Linux 上运行。

先决条件 说明
Azure 订阅 如果你没有订阅,请创建一个 Azure 试用帐户
代码编辑器 需要一个代码编辑器,例如 Visual Studio Code
Azure Functions Core Tools 需要版本 4.0.5611 或更高版本才能运行 v4 编程模型Node.js。
Node.js LTS 请参阅 Azure Functions JavaScript 开发人员指南中支持的 node.js 版本。
Azurite SignalR 绑定需要 Azure 存储。 在本地运行函数时,可以使用本地存储仿真器。
Azure CLI (可选)可以使用 Azure CLI 创建 Azure SignalR 服务实例。

创建 Azure SignalR 服务实例

在本部分中,你将创建一个基本 Azure SignalR 实例来用于你的应用。 以下步骤使用 Azure 门户创建新实例,但你也可以使用 Azure CLI。 有关详细信息,请参阅 Azure SignalR 服务 CLI 参考中的 az signalr create 命令。

  1. 登录 Azure 门户
  2. 在页面的左上角,选择“+ 创建资源” 。
  3. 在“创建资源”页上,在“搜索服务和市场”文本框中,输入“signalr”,然后从列表中选择“SignalR 服务”。
  4. 在“SignalR 服务”页上,选择“创建”。
  5. 在“基本信息”选项卡上,输入新 SignalR 服务实例的基本信息。 输入以下值:
字段 建议的值 描述
订阅 选择订阅 选择要用于创建新的 SignalR 服务实例的订阅。
资源组 创建一个名为 SignalRTestResources 的资源组 为 SignalR 资源选择或创建资源组。 对于本教程,创建新的资源组比使用现有资源组更为合适。 若要在完成本教程后释放资源,请删除资源组。

删除资源组还会删除属于该组的所有资源。 此操作不能撤消。 删除资源组之前,请确保它不包含你希望保留的资源。

有关详细信息,请参阅 Using resource groups to manage your Azure resources(使用资源组管理 Azure 资源)。
资源名称 testsignalr 输入用于 SignalR 资源的唯一资源名称。 如果你的区域中已使用了 testsignalr,请添加一个数字或字符,以将名称设为唯一。

该名称必须是包含 1 到 63 个字符的字符串,只能包含数字、字母和连字符 (-) 字符。 该名称的开头或末尾不能是连字符字符,并且连续的连字符字符无效。
区域 选择你的区域 为新的 SignalR 服务实例选择合适的区域。

Azure SignalR 服务当前并非在所有区域中都可用。 有关详细信息,请参阅 Azure SignalR 服务区域可用性
定价层 选择“更改”,然后选择“免费(仅限开发/测试)”。 选择“选择”以确认你选择的定价层。 Azure SignalR 服务有三个定价层:免费、标准和高级。 教程使用的是免费层,除非在先决条件中另行说明。

若要详细了解各个层级之间的功能差异以及定价,请参阅 Azure SignalR 服务定价
服务模式 选择适当的服务模式 在 Web 应用中托管 SignalR 中心逻辑并使用 SignalR 服务作为代理时,请使用“默认”。 使用无服务器技术(如 Azure Functions)托管 SignalR 中心逻辑时,请使用“无服务器”

“经典”模式仅用于向后兼容,不建议使用。

有关详细信息,请参阅 Azure SignalR 服务中的服务模式

对于 SignalR 教程,你不需要更改“网络”和“标记”选项卡上的设置。

  1. 选择“基本信息”选项卡底部的“查看 + 创建”按钮。
  2. 在“查看 + 创建”选项卡上检查各个值,然后选择“创建”。 部署需要几分钟时间才能完成。
  3. 在部署完成后,选择“转到资源组”按钮。
  4. 在 SignalR 资源页面上,从左侧菜单中选择“设置”下的“密钥”。
  5. 复制主密钥的连接字符串。 在本教程中,稍后你将需要使用此连接字符串来配置你的应用。

设置函数项目

确保已安装 Azure Functions Core Tools。

  1. 打开命令行。
  2. 创建项目目录,然后将其更改为该项目目录。
  3. 运行 Azure Functions func init 命令以初始化新项目。
func init --worker-runtime javascript --language javascript --model V4

创建项目函数

初始化项目后,需要创建函数。 此项目需要三个函数:

  • index:托管客户端的网页。
  • negotiate:允许客户端获取访问令牌。
  • broadcast:使用时间触发器定期将消息广播到所有客户端。

从项目的根目录运行 func new 命令时,Azure Functions Core Tools 会创建函数源文件,并将其存储在以函数名称命名的文件夹中。 根据需要编辑文件,将默认代码替换为应用代码。

创建索引函数

  1. 运行以下命令来创建 index 函数。

    func new -n index -t HttpTrigger
    
  2. 编辑 src/functions/httpTrigger.js,并将内容替换为以下 json 代码:

    const { app } = require('@azure/functions');
    const fs = require('fs').promises;
    const path = require('path');
    
    app.http('index', {
        methods: ['GET', 'POST'],
        authLevel: 'anonymous',
        handler: async (request, context) => {
    
            try {
    
                context.log(`Http function processed request for url "${request.url}"`);
    
                const filePath = path.join(__dirname,'../content/index.html');
                const html = await fs.readFile(filePath);
    
                return {
                    body: html,
                    headers: {
                        'Content-Type': 'text/html'
                    }
                 };
    
            } catch (error) {
                context.log(error);
                return {
                    status: 500,
                    jsonBody: error
                }
            }
        }
    });
    

创建协商函数

  1. 运行以下命令来创建 negotiate 函数。

    func new -n negotiate -t HttpTrigger
    
  2. 编辑 src/functions/negotiate.js,并将内容替换为以下 json 代码:

    const { app, input } = require('@azure/functions');
    
    const inputSignalR = input.generic({
        type: 'signalRConnectionInfo',
        name: 'connectionInfo',
        hubName: 'serverless',
        connectionStringSetting: 'SIGNALR_CONNECTION_STRING',
    });
    
    app.post('negotiate', {
        authLevel: 'anonymous',
        handler: (request, context) => {
            try {
                return { body: JSON.stringify(context.extraInputs.get(inputSignalR)) }
            } catch (error) {
                context.log(error);
                return {
                    status: 500,
                    jsonBody: error
                }
            }
        },
        route: 'negotiate',
        extraInputs: [inputSignalR],
    });
    

创建广播函数。

  1. 运行以下命令来创建 broadcast 函数。

    func new -n broadcast -t TimerTrigger
    
  2. 编辑 src/functions/broadcast.js,并将内容替换为以下代码:

    const { app, output } = require('@azure/functions');
    const getStars = require('../getStars');
    
    var etag = '';
    var star = 0;
    
    const goingOutToSignalR = output.generic({
        type: 'signalR',
        name: 'signalR',
        hubName: 'serverless',
        connectionStringSetting: 'SIGNALR_CONNECTION_STRING',
    });
    
    app.timer('sendMessasge', {
        schedule: '0 * * * * *',
        extraOutputs: [goingOutToSignalR],
        handler: async (myTimer, context) => {
    
            try {
                const response = await getStars(etag);
    
                if(response.etag === etag){
                    console.log(`Same etag: ${response.etag}, no need to broadcast message`);
                    return;
                }
    
                etag = response.etag;
                const message = `${response.stars}`;
    
                context.extraOutputs.set(goingOutToSignalR,
                    {
                        'target': 'newMessage',
                        'arguments': [message]
                    });
            } catch (error) {
                 context.log(error);
            }
    
        }
    });
    

创建 index.html 文件

本应用的客户端界面是网页。 index 函数从 content/index.html 文件中读取 HTML 内容。

  1. 在项目根文件夹中创建名为 content 的文件夹。

  2. 创建 content/index.html 文件。

  3. 将以下内容复制到 content/index.html 文件并保存它:

    <html>
    
    <body>
      <h1>Azure SignalR Serverless Sample</h1>
      <div>Instructions: Goto <a href="https://github.com/Azure/azure-signalr">GitHub repo</a> and star the repository.</div>
      <hr>
      <div>Star count: <div id="messages"></div></div>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
      <script>
        let messages = document.querySelector('#messages');
        const apiBaseUrl = window.location.origin;
        console.log(`apiBaseUrl: ${apiBaseUrl}`);
        const connection = new signalR.HubConnectionBuilder()
            .withUrl(apiBaseUrl + '/api')
            .configureLogging(signalR.LogLevel.Information)
            .build();
          connection.on('newMessage', (message) => {
            console.log(`message: ${message}`);
            document.getElementById("messages").innerHTML = message;
          });
    
          connection.start()
            .catch(console.error);
      </script>
    </body>
    
    </html>
    

设置 Azure 存储

Azure Functions 需要一个存储帐户才能工作。 选择以下两个选项之一:

  • 运行免费的 Azure 存储模拟器
  • 使用 Azure 存储服务。 如果继续使用它,可能会产生费用。
  1. 启动 Azurite 存储模拟器:

    azurite -l azurite -d azurite\debug.log
    
  2. 确保 local.settings.json 中的 AzureWebJobsStorage 设置为 UseDevelopmentStorage=true

将 SignalR 服务连接字符串添加到函数应用设置

操作即将完成。 最后一步是在 Azure 函数应用设置中设置 SignalR 服务连接字符串。

  1. 在 Azure 门户中,转到之前部署的 SignalR 实例。

  2. 选择“密钥”以查看 SignalR 服务实例的连接字符串。

    Azure SignalR 服务“密钥”页的屏幕截图。

  3. 复制主连接字符串并执行命令:

    func settings add AzureSignalRConnectionString "<signalr-connection-string>"
    

在本地运行 Azure 函数应用

在本地环境中运行 Azure 函数应用:

func start

在本地运行 Azure 函数后,转到 http://localhost:7071/api/index。 该页会显示 GitHub Azure/azure-signalr 存储库的当前星级数。 在 GitHub 中为存储库设置星级或取消设置星级时,将会每隔几秒看到刷新的计数。

遇到问题? 请试用故障排除指南

清理资源

如果不打算继续使用此应用,请按照以下步骤删除本快速入门中创建的所有资源,以免产生任何费用:

  1. 在 Azure 门户的最左侧选择“资源组”,,然后选择创建的资源组。 或者,可以使用搜索框按名称查找资源组。

  2. 在打开的窗口中选择资源组,然后单击“删除资源组”。

  3. 在新窗口中键入要删除的资源组的名称,然后单击“删除”

示例代码

可以从 GitHub 存储库获取本文中使用的所有代码:

后续步骤

在本快速入门中,你在 localhost 中生成并运行了一个实时无服务器应用程序。 接着,详细了解了如何通过 SignalR 服务在客户端与 Azure Functions 之间进行双向通信。