Compartilhar via


Tutorial: Criar aplicativo de chat com o Azure Functions no modo sem servidor (versão prévia)

Este tutorial explica como criar um Web PubSub para Socket.IO serviço no Modo sem Servidor e criar um aplicativo de chat integrando-se 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, você não pode integrar o Web PubSub para Socket.IO no modo padrão com o Azure Function.

Importante

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

Uma cadeia de conexão inclui as informações de autorização necessárias para que o seu aplicativo acesse o serviço Azure Web PubSub. A chave de acesso dentro da cadeia de conexão é semelhante a uma senha raiz para o serviço. Em ambientes de produção, sempre proteja suas chaves de acesso. Use o Azure Key Vault para gerenciar e girar suas chaves com segurança e proteger sua conexão com WebPubSubServiceClient.

Evite distribuir chaves de acesso para outros usuários, fazer hard-coding com elas ou salvá-las em qualquer lugar em texto sem formatação que seja acessível a outras pessoas. Gire suas chaves se você acredita que elas 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 pasta SocketIOProject:

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

    Este comando cria um projeto de JavaScript. E insira a pasta SocketIOProject para executar os comandos a seguir.

  3. Atualmente, o Pacote de Function não inclui Socket.IO Vinculação do Function, portanto, você precisa adicionar manualmente o pacote.

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

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Execute 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 usada para gerar pontos de extremidade e tokens para o cliente acessar o 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 gatilho HTTP e SocketIONegotiation associação de saída, o que significa que você pode usar uma chamada HTTP para disparar 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 código a seguir:

    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 usa SocketIOTrigger para ser disparado por uma mensagem de cliente Socket.IO e usar a vinculação SocketIO para transmitir mensagens para o namespace.

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

    1. Crie uma pasta public em src/.

    2. Crie um arquivo html index.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 código a seguir:

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

Para 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 Azure Functions exige uma conta de armazenamento para funcionar, mesmo em execução local. Escolha uma das duas opções a seguir:

  • Execute o emulador Azurite gratuito.
  • Use o serviço de Armazenamento do Microsoft Azure. Isso poderá incorrer em custos se você continuar a usá-lo.
  1. Instalar o Azurite
npm install -g azurite
  1. Inicie o emulador de armazenamento do Azurite:
azurite -l azurite -d azurite\debug.log
  1. Garanta que o AzureWebJobsStorage em local.settings.json está definido como UseDevelopmentStorage=true.

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

  1. Adicionar cadeia de conexão ao APLICATIVO Function:
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 da CLI do Azure

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

A saída contém primaryConnectionString e secondaryConnectionString que estão disponíveis.

Configurar túnel

No modo sem servidor, o serviço usa webhooks para disparar a função. Quando você tenta executar o aplicativo localmente, um problema crucial é permitir que o serviço possa acessar seu ponto de extremidade de função local.

Uma maneira mais fácil de conseguir é usar a Ferramenta de Túnel

  1. Instalar a Ferramenta de Túnel:

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

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

    --upstream é a URL que o Azure Function 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 o aplicativo de exemplo

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

func start

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

Captura de tela do aplicativo de chat sem servidor.

Próximas etapas

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