Tutorial: Criar um aplicativo de chat em tempo real sem servidor com o Azure Functions e o serviço Azure Web PubSub

O serviço Azure Web PubSub ajuda você a criar aplicativos Web de mensagens em tempo real usando o WebSockets e o padrão de publicação-assinatura com facilidade. O Azure Functions é uma plataforma sem servidor que permite executar seu código sem gerenciar qualquer infraestrutura. Neste tutorial, você aprende como usar o serviço Azure Web PubSub e o Azure Functions para criar um aplicativo sem servidor com mensagens em tempo real e o padrão de publicação-assinatura.

Neste tutorial, você aprenderá como:

  • Criar um aplicativo de chat em tempo real sem servidor
  • Trabalhar com as associações de gatilho de função e as associações de saída do Web PubSub
  • Implantar a função no aplicativo de funções do Azure
  • Configurar a autenticação do Azure
  • Configurar o manipulador de eventos do Web PubSub para rotear eventos e mensagens para o aplicativo

Pré-requisitos

Caso você não tenha uma assinatura do Azure, crie uma conta gratuita do Azure antes de começar.

Entrar no Azure

Entre no portal do Azure em https://portal.azure.com/ com sua conta do Azure.

Criar uma instância do serviço Azure Web PubSub

Seu aplicativo se conectará a uma instância do serviço Web PubSub no Azure.

  1. Selecione no botão Novo localizado no canto superior esquerdo do portal do Azure. Na tela Novo, digite Web PubSub na caixa de pesquisa e pressione ENTER. (Você também pode pesquisar o Azure Web PubSub na categoria Web.)

    Captura de tela da pesquisa do Azure Web PubSub no portal.

  2. Selecione Web PubSub nos resultados da pesquisa e escolha Criar.

  3. Insira as configurações a seguir.

    Configuração Valor sugerido Descrição
    Nome do recurso Nome globalmente exclusivo O Nome globalmente exclusivo que identifica a nova instância do serviço Azure Web PubSub. Os caracteres válidos são a-z, A-Z, 0-9 e -.
    Assinatura Sua assinatura A assinatura do Azure na qual essa nova instância do serviço Azure Web PubSub será criada.
    Grupo de Recursos myResourceGroup Nome do novo grupo de recursos no qual a instância do serviço Azure Web PubSub será criada.
    Localidade Oeste dos EUA Selecione uma região perto de você.
    Tipo de preços Grátis Primeiro, você pode experimentar o serviço Azure Web PubSub gratuitamente. Saiba mais detalhes sobre os tipos de preços do serviço Azure Web PubSub
    Contagem de unidades - A contagem de unidades especifica quantas conexões a instância do serviço Azure Web PubSub pode aceitar. Cada unidade dá suporte a, no máximo, 1.000 conexões simultâneas. Só é configurável na camada Standard.

    Captura de tela da criação da instância do Azure Web PubSub no Portal.

  4. Selecione Criar para começar a implantar a instância do serviço Azure Web PubSub.

Criar as funções

  1. Verifique se você tem o Azure Functions Core Tools instalado. E, em seguida, criar um diretório vazio para o projeto. Execute o comando neste diretório de trabalho.

    func init --worker-runtime javascript --model V4
    
  2. Instale o Microsoft.Azure.WebJobs.Extensions.WebPubSub.

    Confirme e atualize o extensionBundle do host.json para a versão 4.* ou posterior, para obter suporte ao Web PubSub.

    {
      "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[4.*, 5.0.0)"
      }
    }
    
  3. Crie uma função index para ler e hospedar uma página da Web estática para clientes.

    func new -n index -t HttpTrigger
    
    • Atualize src/functions/index.js e copie os seguintes códigos.
      const { app } = require('@azure/functions');
      const { readFile } = require('fs/promises');
      
      app.http('index', {
          methods: ['GET', 'POST'],
          authLevel: 'anonymous',
          handler: async (context) => {
              const content = await readFile('index.html', 'utf8', (err, data) => {
                  if (err) {
                      context.err(err)
                      return
                  }
              });
      
              return { 
                  status: 200,
                  headers: { 
                      'Content-Type': 'text/html'
                  }, 
                  body: content, 
              };
          }
      });
      
  4. Crie uma função negotiate para ajudar os clientes a obterem a URL de conexão do serviço com o token de acesso.

    func new -n negotiate -t HttpTrigger
    

    Observação

    Neste exemplo, usamos o cabeçalho de identidade do usuário do Microsoft Entra ID x-ms-client-principal-name para recuperar userId. Isso não funcionará em uma função local. Você pode torná-lo vazio ou usar outras formas de obter ou gerar a userId na execução local. Por exemplo, permita que o cliente digite um nome de usuário e o transmita em uma consulta como ?user={$username} quando chamar a função negotiate para obter a URL de conexão do serviço. Na função negotiate, defina userId com o valor {query.user}.

    • Atualize src/functions/negotiate e copie os seguintes códigos.
      const { app, input } = require('@azure/functions');
      
      const connection = input.generic({
          type: 'webPubSubConnection',
          name: 'connection',
          userId: '{headers.x-ms-client-principal-name}',
          hub: 'simplechat'
      });
      
      app.http('negotiate', {
          methods: ['GET', 'POST'],
          authLevel: 'anonymous',
          extraInputs: [connection],
          handler: async (request, context) => {
              return { body: JSON.stringify(context.extraInputs.get('connection')) };
          },
      });
      
  5. Crie uma função message para transmitir mensagens do cliente por meio do serviço.

    func new -n message -t HttpTrigger
    
    • Atualize src/functions/message.js e copie os seguintes códigos.
      const { app, output, trigger } = require('@azure/functions');
      
      const wpsMsg = output.generic({
          type: 'webPubSub',
          name: 'actions',
          hub: 'simplechat',
      });
      
      const wpsTrigger = trigger.generic({
          type: 'webPubSubTrigger',
          name: 'request',
          hub: 'simplechat',
          eventName: 'message',
          eventType: 'user'
      });
      
      app.generic('message', {
          trigger: wpsTrigger,
          extraOutputs: [wpsMsg],
          handler: async (request, context) => {
              context.extraOutputs.set(wpsMsg, [{
                  "actionName": "sendToAll",
                  "data": `[${context.triggerMetadata.connectionContext.userId}] ${request.data}`,
                  "dataType": request.dataType
              }]);
      
              return {
                  data: "[SYSTEM] ack.",
                  dataType: "text",
              };
          }
      });
      
  6. Adicione a página única do cliente index.html na pasta raiz do projeto e copie o conteúdo.

    <html>
      <body>
        <h1>Azure Web PubSub Serverless Chat App</h1>
        <div id="login"></div>
        <p></p>
        <input id="message" placeholder="Type to chat..." />
        <div id="messages"></div>
        <script>
          (async function () {
            let authenticated = window.location.href.includes(
              "?authenticated=true"
            );
            if (!authenticated) {
              // auth
              let login = document.querySelector("#login");
              let link = document.createElement("a");
              link.href = `${window.location.origin}/.auth/login/aad?post_login_redirect_url=/api/index?authenticated=true`;
              link.text = "login";
              login.appendChild(link);
            } else {
              // negotiate
              let messages = document.querySelector("#messages");
              let res = await fetch(`${window.location.origin}/api/negotiate`, {
                credentials: "include",
              });
              let url = await res.json();
              // connect
              let ws = new WebSocket(url.url);
              ws.onopen = () => console.log("connected");
              ws.onmessage = (event) => {
                let m = document.createElement("p");
                m.innerText = event.data;
                messages.appendChild(m);
              };
              let message = document.querySelector("#message");
              message.addEventListener("keypress", (e) => {
                if (e.charCode !== 13) return;
                ws.send(message.value);
                message.value = "";
              });
            }
          })();
        </script>
      </body>
    </html>
    

Criar e implantar o Aplicativo de Funções do Azure

Antes de poder implantar o código da função no Azure, você precisa criar três recursos:

  • Um grupo de recursos, que é um contêiner lógico para recursos relacionados.
  • Uma conta de armazenamento, que é usada para manter o estado e outras informações sobre suas funções.
  • Um aplicativo de funções, que fornece o ambiente para a execução do código de função. Um aplicativo de funções é mapeado para seu projeto de função local e permite agrupar funções como uma unidade lógica para facilitar o gerenciamento, a implantação e o compartilhamento de recursos.

Use os comandos a seguir para criar esses itens.

  1. Se você ainda não tiver feito isso, entre no Azure:

    az login
    
  2. Crie um grupo de recursos ou ignore essa etapa reutilizando o do serviço do Azure Web PubSub:

    az group create -n WebPubSubFunction -l <REGION>
    
  3. Crie uma conta de armazenamento para uso geral no grupo de recursos e na região:

    az storage account create -n <STORAGE_NAME> -l <REGION> -g WebPubSubFunction
    
  4. Criar o aplicativo de funções no Azure:

    az functionapp create --resource-group WebPubSubFunction --consumption-plan-location <REGION> --runtime node --runtime-version 18 --functions-version 4 --name <FUNCIONAPP_NAME> --storage-account <STORAGE_NAME>
    

    Observação

    Verifique a documentação das versões de tempo de execução do Azure Functions para definir o parâmetro --runtime-version para o valor com suporte.

  5. Implantar o projeto de funções no Azure:

    Depois de criar com sucesso o aplicativo de funções no Azure, você estará pronto para implantar o projeto de funções local usando o comando func azure functionapp publish.

    func azure functionapp publish <FUNCIONAPP_NAME>
    
  6. Configure o WebPubSubConnectionString para o aplicativo de funções:

    Primeiro, encontre seu recurso do Web PubSub no portal do Azure e copie a cadeia de conexão em Chaves. Depois, acesse as configurações do Aplicativo de Funções em Portal do Azure ->Configurações ->Configuração. Além disso, adicione um novo item em Configurações de aplicativo, com um nome igual a WebPubSubConnectionString e o valor como a cadeia de conexão do recurso do Web PubSub.

Configurar o serviço Web PubSub Event Handler

Neste exemplo, usamos WebPubSubTrigger para escutar as solicitações upstream do serviço. Portanto, o Web PubSub precisa saber as informações do ponto de extremidade da função para enviar solicitações de cliente de destino. Além disso, o aplicativo de funções do Azure exige uma chave do sistema para segurança em relação aos métodos de webhook específicos da extensão. Na etapa anterior, depois de implantarmos o aplicativo de funções com as funções message, obteremos a chave do sistema.

Acesse Portal do Azure -> localize seu recurso do Aplicativo de Funções ->Chaves do aplicativo ->Chaves do sistema ->webpubsub_extension. Copie o valor como <APP_KEY>.

Captura de tela da obtenção das chaves do sistema da função.

Defina Event Handler no serviço Azure Web PubSub. Acesse Portal do Azure -> localize seu recurso do Web PubSub ->Configurações. Adicione um novo mapeamento de configurações de hub para a função em uso. Substitua o <FUNCTIONAPP_NAME> e a <APP_KEY> pelas suas informações.

  • Nome do hub: simplechat
  • Modelo de URL: https://<FUNCTIONAPP_NAME>.azurewebsites.net/runtime/webhooks/webpubsub?code=<APP_KEY>
  • Padrão de evento do usuário: *
  • Eventos do Sistema: – (não é necessário configurar essa opção neste exemplo)

Captura de tela da configuração do manipulador de eventos.

Configurar o para habilitar a autenticação de cliente

Acesse Portal do Azure -> localize seu recurso do Aplicativo de Funções ->Autenticação. Clique em Add identity provider. Defina as configurações de autenticação do Serviço de Aplicativo como Permitir o acesso não autenticado, de modo que a página de índice do cliente possa ser visitada por usuários anônimos antes de redirecioná-los para autenticação. Em seguida, Salve.

Aqui, escolhemos Microsoft como o provedor de identidade, que usa x-ms-client-principal-name como userId na função negotiate. Além disso, você pode configurar outros provedores de identidade seguindo os links. Não se esqueça de atualizar o valor userId na função negotiate de acordo.

Experimentar o aplicativo

Agora você poderá testar sua página no aplicativo de funções: https://<FUNCTIONAPP_NAME>.azurewebsites.net/api/index. Veja o instantâneo.

  1. Clique em login para se autenticar.
  2. Digite uma mensagem na caixa de entrada para conversar.

Na função de mensagem, transmitimos a mensagem do chamador para todos os clientes e retornar o chamador com a mensagem [SYSTEM] ack. Portanto, podemos saber no instantâneo de exemplo de chat que as primeiras quatro mensagens são do cliente atual e as duas últimas mensagens são de outro cliente.

Captura de tela do exemplo de chat.

Limpar recursos

Se você não pretende continuar usando este aplicativo, exclua todos os recursos criados por este documento com as seguintes etapas para não gerar custos:

  1. No portal do Azure, selecione Grupos de recursos na extremidade esquerda, depois selecione o recurso de grupo que você criou. Você poderá usar a caixa de pesquisa para localizar o grupo de recursos pelo nome.

  2. Na janela que será aberta, selecione o grupo de recursos e escolha Excluir grupo de recursos.

  3. Na nova janela, digite o nome do grupo de recursos a ser excluído e selecione Excluir.

Próximas etapas

Neste início rápido, você aprendeu a executar um aplicativo de chat sem servidor. Agora, você pode começar a criar seu próprio aplicativo.