Compartir a través de


Tutorial: Compilación de una aplicación de chat con Azure Function en modo sin servidor (versión preliminar)

En este tutorial se explica cómo crear una web PubSub para Socket.IO servicio en modo sin servidor y crear una aplicación de chat que se integre con Azure Function.

Busque ejemplos de código completos que se usan en este tutorial:

Importante

El modo predeterminado necesita un servidor persistente, no puede integrar Web PubSub para Socket.IO en modo predeterminado con Azure Function.

Importante

Las cadenas de conexión sin procesar solo aparecen en este artículo con fines de demostración.

Una cadena de conexión incluye la información de autorización necesaria para que la aplicación acceda al servicio Azure Web PubSub. La clave de acceso dentro de la cadena de conexión es similar a una contraseña raíz para el servicio. En entornos de producción, proteja siempre las claves de acceso. Use Azure Key Vault para administrar y rotar las claves de forma segura y proteja la conexión con WebPubSubServiceClient.

Evite distribuirlas a otros usuarios, codificarlas de forma rígida o guardarlas en un archivo de texto sin formato al que puedan acceder otros usuarios. Rote sus claves si cree que se han puesto en peligro.

Requisitos previos

Crear un web PubSub para Socket.IO recurso en modo sin servidor

Para crear un Web PubSub para Socket.IO, puede usar el siguiente comando de la CLI de Azure:

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

Creación de un proyecto de Azure Function localmente

Debe seguir los pasos para iniciar un proyecto local de Azure Functions.

  1. Siga estos pasos para instalar la Herramienta principal de Azure Function más reciente

  2. En la ventana de terminal, o desde un símbolo del sistema, ejecute el siguiente comando para crear un proyecto en la carpeta SocketIOProject:

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

    Este comando crea un proyecto de JavaScript. Y escriba la carpeta SocketIOProject para ejecutar los siguientes comandos.

  3. Actualmente, la agrupación de funciones no incluye Socket.IO enlace de funciones, por lo que debe agregar manualmente el paquete.

    1. Para eliminar la referencia de agrupación de funciones, edite el archivo host.json y quite las líneas siguientes.

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

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Cree una función para la negociación. La función de negociación que se usa para generar puntos de conexión y tokens para que el cliente acceda al servicio.

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

    Abra el archivo en src/functions/negotiate.js y reemplace por el código siguiente:

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

    Este paso crea una función negotiate con un desencadenador HTTP y un enlace de salida SocketIONegotiation, lo que significa que puede usar una llamada HTTP para activar la función y devolver un resultado de negociación generado por el enlace SocketIONegotiation.

  5. Cree una función para entregar mensajes.

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

    Abra el archivo src/functions/message.js y reemplace por el código siguiente:

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

    Esto usa SocketIOTrigger para desencadenarse mediante un mensaje de cliente de Socket.IO y usar SocketIO el enlace para difundir mensajes al espacio de nombres.

  6. Cree una función para devolver un html de índice para visitar.

    1. Cree una carpeta public en src/.

    2. Cree un archivo html index.html con el siguiente contenido.

      <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 devolver la página HTML, cree una función y copie códigos:

      func new --template "Http Trigger" --name index
      
    4. Abra el archivo src/functions/index.js y reemplace por el código siguiente:

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

Ejecución local de la aplicación

Una vez preparado el código, siga las instrucciones para ejecutar el ejemplo.

Configuración de Azure Storage para Azure Function

Azure Functions requiere que una cuenta de almacenamiento funcione incluso ejecutándose en el entorno local. Elija cualquiera de las dos opciones siguientes:

  • Ejecute el emulador de Azurite gratuito.
  • Use el servicio Azure Storage. Esto puede incurrir en costos si sigue utilizándolo.
  1. Instalación de Azurite
npm install -g azurite
  1. Inicie el emulador de almacenamiento de Azurite:
azurite -l azurite -d azurite\debug.log
  1. Asegúrese de que AzureWebJobsStorage en local.settings.json esté establecido en UseDevelopmentStorage=true.

Configuración de Web PubSub para Socket.IO

  1. Agregue una cadena de conexión a Function APP:
func settings add WebPubSubForSocketIOConnectionString "<connection string>"
  1. Agregar la configuración del centro a 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="*"

El comando de la CLI de Azure puede obtener la cadena de conexión

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

La salida contiene primaryConnectionString y secondaryConnectionString, y está disponible.

Configuración del túnel

En el modo sin servidor, el servicio usa webhooks para desencadenar la función. Al intentar ejecutar la aplicación localmente, un problema crucial es permitir que el servicio pueda acceder al punto de conexión de la función local.

Una manera más fácil de lograr es usar Herramienta de túnel

  1. Instalación de la herramienta Tunnel:

    npm install -g @azure/web-pubsub-tunnel-tool
    
  2. Ejecución del túnel

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

    El --upstream es la dirección URL que expone la función local de Azure. El puerto puede ser diferente y puede comprobar la salida al iniciar la función en el paso siguiente.

Ejecución de aplicación de ejemplo

Después de ejecutar la herramienta de túnel, puede ejecutar function App localmente:

func start

Y visite la página web en http://localhost:7071/api/index.

Recorte de pantalla de la aplicación de chat sin servidor.

Pasos siguientes

A continuación, puede intentar usar Bicep para implementar la aplicación en línea con la autenticación basada en identidad: