Поделиться через


Руководство по созданию приложения чата с помощью функции Azure в бессерверном режиме (предварительная версия)

В этом руководстве объясняется, как создать службу публикации в режиме реального времени для Socket.IO в режиме без сервера и разработать приложение для чата, интегрированное с функцией Azure.

Найдите полные примеры кода, используемые в этом руководстве:

Внимание

В режиме по умолчанию требуется постоянный сервер, вы не можете интегрировать Web PubSub для Socket.IO в режиме по умолчанию с функцией Azure.

Внимание

Необработанные строки подключения отображаются в этой статье в демонстрационных целях.

Строка подключения содержит сведения об авторизации, требуемые для доступа приложения к службе Azure Web PubSub. Ключ доступа в строке подключения аналогичен паролю привилегированного пользователя для службы. В рабочих средах всегда защищать ключи доступа. Используйте Azure Key Vault для безопасного управления и ротации ключей, а также для защиты вашего подключения с помощью WebPubSubServiceClient.

Старайтесь не распространять ключи доступа среди других пользователей, жестко программировать их или где-то сохранять в виде обычного текста в открытом доступе для других пользователей. Меняйте свои ключи периодически, если считаете, что они могли быть скомпрометированы.

Предварительные условия

Создайте ресурс Web PubSub для Socket.IO в бессерверном режиме.

Чтобы создать web PubSub для Socket.IO, можно использовать следующую команду Azure CLI :

az webpubsub create -g <resource-group> -n <resource-name>--kind socketio --service-mode serverless --sku Premium_P1

Создание проекта функции Azure локально

Выполните действия, чтобы инициировать локальный проект функции Azure.

  1. Следуйте шагу, чтобы установить последнюю версию основного средства функции Azure.

  2. В окне терминала или из командной строки выполните следующую команду, чтобы создать проект в папке SocketIOProject :

    func init SocketIOProject --worker-runtime javascript --model V4
    

    Эта команда создает проект JavaScript. Введите папку SocketIOProject , чтобы выполнить следующие команды.

  3. В настоящее время пакет функций не включает привязку функции Socket.IO, поэтому необходимо вручную добавить пакет.

    1. Чтобы исключить ссылку на пакет функций, измените файл host.json и удалите следующие строки.

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Выполните команду:

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Создайте функцию для согласования. Функция согласования, используемая для создания конечных точек и маркеров для доступа клиента к службе.

    func new --template "Http Trigger" --name negotiate
    

    Откройте файл src/functions/negotiate.js и замените приведенным ниже кодом:

    const { app, input } = require('@azure/functions');
    
    const socketIONegotiate = input.generic({
        type: 'socketionegotiation',
        direction: 'in',
        name: 'result',
        hub: 'hub'
    });
    
    async function negotiate(request, context) {
        let result = context.extraInputs.get(socketIONegotiate);
        return { jsonBody: result };
    };
    
    // Negotiation
    app.http('negotiate', {
        methods: ['GET', 'POST'],
        authLevel: 'anonymous',
        extraInputs: [socketIONegotiate],
        handler: negotiate
    });
    

    На этом шаге создается функция negotiate с триггером HTTP и SocketIONegotiation выходной привязкой, что означает, что можно использовать HTTP-вызов для активации функции и возврата результата согласования, созданного SocketIONegotiation привязкой.

  5. Создайте функцию для передачи сообщений.

    func new --template "Http Trigger" --name message
    

    Откройте файл src/functions/message.js и замените следующим кодом:

    const { app, output, trigger } = require('@azure/functions');
    
    const socketio = output.generic({
    type: 'socketio',
    hub: 'hub',
    })
    
    async function chat(request, context) {
        context.extraOutputs.set(socketio, {
        actionName: 'sendToNamespace',
        namespace: '/',
        eventName: 'new message',
        parameters: [
            context.triggerMetadata.socketId,
            context.triggerMetadata.message
        ],
        });
    }
    
    // Trigger for new message
    app.generic('chat', {
        trigger: trigger.generic({
            type: 'socketiotrigger',
            hub: 'hub',
            eventName: 'chat',
            parameterNames: ['message'],
        }),
        extraOutputs: [socketio],
        handler: chat
    });
    

    Эта конструкция использует SocketIOTrigger для срабатывания сообщения клиента Socket.IO и применяет привязку SocketIO для трансляции сообщений в пространстве имен.

  6. Создайте функцию, возвращающую индексную HTML-страницу для посещения.

    1. Создайте папку public в разделе src/.

    2. Создайте HTML-файл index.html со следующим содержимым.

      <html>
      
      <body>
      <h1>Socket.IO Serverless Sample</h1>
      <div id="chatPage" class="chat-container">
          <div class="chat-input">
              <input type="text" id="chatInput" placeholder="Type your message here...">
              <button onclick="sendMessage()">Send</button>
          </div>
          <div id="chatMessages" class="chat-messages"></div>
      </div>
      <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
      <script>
          function appendMessage(message) {
          const chatMessages = document.getElementById('chatMessages');
          const messageElement = document.createElement('div');
          messageElement.innerText = message;
          chatMessages.appendChild(messageElement);
          hatMessages.scrollTop = chatMessages.scrollHeight;
          }
      
          function sendMessage() {
          const message = document.getElementById('chatInput').value;
          if (message) {
              document.getElementById('chatInput').value = '';
              socket.emit('chat', message);
          }
          }
      
          async function initializeSocket() {
          const negotiateResponse = await fetch(`/api/negotiate`);
          if (!negotiateResponse.ok) {
              console.log("Failed to negotiate, status code =", negotiateResponse.status);
              return;
          }
          const negotiateJson = await negotiateResponse.json();
          socket = io(negotiateJson.endpoint, {
              path: negotiateJson.path,
              query: { access_token: negotiateJson.token }
          });
      
          socket.on('new message', (socketId, message) => {
              appendMessage(`${socketId.substring(0,5)}: ${message}`);
          })
          }
      
          initializeSocket();
      </script>
      </body>
      
      </html>
      
    3. Чтобы вернуть HTML-страницу, создайте функцию и скопируйте коды.

      func new --template "Http Trigger" --name index
      
    4. Откройте файл src/functions/index.js и замените следующим кодом:

      const { app } = require('@azure/functions');
      
      const fs = require('fs').promises;
      const path = require('path')
      
      async function index(request, context) {
          try {
              context.log(`HTTP function processed request for url "${request.url}"`);
      
              const filePath = path.join(__dirname,'../public/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
              }
          }
      };
      
      app.http('index', {
          methods: ['GET', 'POST'],
          authLevel: 'anonymous',
          handler: index
      });
      
      

Как локально запустить приложение

После подготовки кода выполните инструкции по выполнению примера.

Настройка хранилища Azure для функции Azure

Функциям Azure требуется учетная запись для хранения данных, чтобы работать, даже если они запускаются локально. Выберите один из двух следующих вариантов:

  • Запустите бесплатный эмулятор Azurite.
  • Используйте службу хранилища Azure. Это может привести к затратам, если вы продолжаете использовать его.
  1. Установка Azurite
npm install -g azurite
  1. Запустите эмулятор хранилища Azurite:
azurite -l azurite -d azurite\debug.log
  1. Убедитесь, что AzureWebJobsStorage в local.settings.json установлено как UseDevelopmentStorage=true.

Настройка конфигурации Web PubSub для Socket.IO

  1. Добавьте строку подключения в функциональное приложение:
func settings add WebPubSubForSocketIOConnectionString "<connection string>"
  1. Добавить настройки концентратора в Web PubSub для Socket.IO
az webpubsub hub create -n <resource name> -g <resource group> --hub-name hub --event-handler url-template="tunnel:///runtime/webhooks/socketio" user-event-pattern="*"

Строку подключения можно получить с помощью команды Azure CLI

az webpubsub key show -g <resource group> -n <resource name>

Выходные данные содержат primaryConnectionString и secondaryConnectionString, и оба доступны.

Настройка туннеля

В бессерверном режиме служба использует веб-перехватчики для активации функции. При попытке локального запуска приложения важно разрешить службе получить доступ к локальной конечной точке функции.

Самый простой способ добиться этого — использовать инструмент туннеля

  1. Установка средства туннеля:

    npm install -g @azure/web-pubsub-tunnel-tool
    
  2. Запуск туннеля

    awps-tunnel run --hub hub --connection "<connection string>" --upstream http://127.0.0.1:7071
    

    Это --upstream URL-адрес, который отображает локальная функция Azure. Порт может отличаться, и вы можете проверить выходные данные при запуске функции на следующем шаге.

Запуск примера приложения

После запуска туннеля можно локально запустить функциональное приложение:

func start

И посетите веб-страницу по адресу http://localhost:7071/api/index.

Снимок экрана: бессерверное приложение чата.

Следующие шаги

Затем можно попытаться использовать Bicep для развертывания приложения в Сети с проверкой подлинности на основе удостоверений: