Tutorial: Criar aplicativo de chat com a Função do Azure no Modo Sem Servidor (Visualização)

Este tutorial orienta você sobre como criar um Web PubSub para Socket.IO serviço no Modo sem Servidor e criar um aplicativo de chat integrado ao Azure Function.

Encontre exemplos de código completo que são usados neste tutorial:

Importante

O Modo Padrão precisa de um servidor persistente, não é possível integrar o Web PubSub para Socket.IO no modo padrão com a Função do Azure.

Importante

As cadeias de conexão brutas aparecem neste artigo apenas para fins de demonstração.

Uma cadeia de ligação inclui a informação de autorização que a sua aplicação precisa para aceder ao serviço Azure Web PubSub. A chave de acesso na cadeia de ligação funciona como uma palavra-passe root para o seu serviço. Em ambientes de produção, proteja sempre as suas chaves de acesso. Use o Azure Key Vault para gerenciar e girar suas chaves com segurança e proteger sua conexão com WebPubSubServiceCliento .

Evite distribuir chaves de acesso para outros usuários, codificá-las ou salvá-las em qualquer lugar em texto simples acessível a outras pessoas. Rode as suas chaves se acreditar que podem ter sido comprometidas.

Pré-requisitos

Criar um recurso Web PubSub para Socket.IO no modo sem servidor

Para criar um Web PubSub para Socket.IO, você pode usar o seguinte comando da CLI do Azure:

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

Criar um projeto do Azure Function localmente

Você deve seguir as etapas para iniciar um projeto local do Azure Function.

  1. Siga a etapa para instalar a ferramenta principal mais recente do Azure Function

  2. Na janela do terminal ou em um prompt de comando, execute o seguinte comando para criar um projeto na SocketIOProject pasta:

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

    Este comando cria um projeto JavaScript. E digite a pasta SocketIOProject para executar os seguintes comandos.

  3. Atualmente, o Pacote de Funções não inclui Vinculação de Função Socket.IO, pelo que é necessário adicionar o pacote manualmente.

    1. Para eliminar a referência do pacote de funções, edite o arquivo host.json e remova as seguintes linhas.

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Executar o comando:

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Crie uma função para negociação. A função de negociação utilizada para gerar endpoints e tokens que permitem ao cliente aceder ao serviço.

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

    Abra o arquivo em src/functions/negotiate.js e substitua pelo seguinte código:

    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
    });
    

    Esta etapa cria uma função negotiate com Disparador HTTP e SocketIONegotiation associação de saída, o que significa que pode usar uma chamada HTTP para acionar a função e retornar um resultado de negociação gerado pela SocketIONegotiation associação.

  5. Crie uma função para entregar mensagens.

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

    Abra o arquivo src/functions/message.js e substitua pelo seguinte código:

    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
    });
    

    Isso utiliza SocketIOTrigger para ser acionado por uma mensagem de cliente Socket.IO e utiliza a associação SocketIO para transmitir mensagens para o espaço de nomes.

  6. Crie uma função para retornar um índice html para visitar.

    1. Crie uma pasta public em src/.

    2. Crie um arquivo index.html html com o seguinte conteúdo.

      <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. Para retornar a página HTML, crie uma função e copie códigos:

      func new --template "Http Trigger" --name index
      
    4. Abra o arquivo src/functions/index.js e substitua pelo seguinte código:

      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
      });
      
      

Como executar o aplicativo localmente

Depois que o código é preparado, siga as instruções para executar o exemplo.

Configurar o Armazenamento do Azure para o Azure Function

O Funções do Azure requer uma conta de armazenamento para funcionar mesmo em execução local. Escolha uma das duas opções a seguir:

  • Execute o emulador gratuito do Azurite.
  • Use o serviço de Armazenamento do Azure. Este poderá implicar custos se continuar a utilizá-lo.
  1. Instalar o Azurite
npm install -g azurite
  1. Inicie o emulador de armazenamento Azurite:
azurite -l azurite -d azurite\debug.log
  1. Certifique-se de que o AzureWebJobsStorage in local.settings.json definido como UseDevelopmentStorage=true.

Configurar a configuração do Web PubSub para Socket.IO

  1. Adicione a cadeia de conexão à aplicação de função:
func settings add WebPubSubForSocketIOConnectionString "<connection string>"
  1. Adicionar configurações de hub ao Web PubSub para 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="*"

A cadeia de conexão pode ser obtida pelo comando CLI do Azure

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

A saída contém primaryConnectionString e secondaryConnectionString, e qualquer um deles está disponível.

Configurar túnel

No modo sem servidor, o serviço usa webhooks para acionar a função. Quando tentas executar a aplicação localmente, um problema crucial é garantir que o serviço possa aceder ao teu endpoint de função local.

Uma maneira mais fácil de conseguir isso é usar a Tunnel Tool

  1. Instale a Ferramenta Tunnel:

    npm install -g @azure/web-pubsub-tunnel-tool
    
  2. Operar o túnel

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

    A --upstream é a URL que a Função do Azure local expõe. A porta pode ser diferente e você pode verificar a saída ao iniciar a função na próxima etapa.

Executar aplicativo de exemplo

Depois que a ferramenta de túnel estiver em execução, você poderá executar o aplicativo de função localmente:

func start

E visite a página em http://localhost:7071/api/index.

Captura de ecrã da aplicação de chat sem servidor.

Próximos passos

Em seguida, você pode tentar usar o Bicep para implantar o aplicativo online com autenticação baseada em identidade: