Samouczek: tworzenie aplikacji czatu za pomocą funkcji platformy Azure w trybie bezserwerowym (wersja zapoznawcza)

W tym samouczku przedstawiono sposób tworzenia usługi Web PubSub dla usługi Socket.IO w trybie bezserwerowym i tworzenia aplikacji czatu integrującej się z funkcją platformy Azure.

Znajdź pełne przykłady kodu wykorzystane w tym samouczku:

Ważne

Tryb domyślny wymaga trwałego serwera. Nie można zintegrować usługi Web PubSub dla Socket.IO w trybie domyślnym z funkcją platformy Azure.

Ważne

Nieprzetworzone parametry połączenia są wyświetlane tylko w tym artykule w celach demonstracyjnych.

parametry połączenia zawiera informacje autoryzacyjne potrzebne Twojej aplikacji, aby uzyskać dostęp do usługi Azure Web PubSub. Klucz dostępu w parametry połączenia działa jak hasło root dla Twojej usługi. W środowiskach produkcyjnych zawsze chroń klucze dostępu. Użyj usługi Azure Key Vault, aby bezpiecznie zarządzać kluczami i obracać je oraz zabezpieczać połączenie za pomocą usługi WebPubSubServiceClient.

Unikaj dystrybuowania kluczy dostępu do innych użytkowników, kodowania ich lub zapisywania ich w dowolnym miejscu w postaci zwykłego tekstu, który jest dostępny dla innych użytkowników. Obróć klucze, jeśli uważasz, że zostały naruszone.

Wymagania wstępne

Tworzenie usługi Web PubSub dla zasobu Socket.IO w trybie bezserwerowym

Aby utworzyć usługę Web PubSub dla Socket.IO, możesz użyć następującego polecenia interfejsu wiersza polecenia platformy Azure:

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

Lokalne tworzenie projektu funkcji platformy Azure

Należy wykonać kroki, aby zainicjować lokalny projekt funkcji platformy Azure.

  1. Wykonaj kroki, aby zainstalować najnowsze narzędzie podstawowe funkcji platformy Azure

  2. W oknie terminalu lub w wierszu polecenia uruchom następujące polecenie, aby utworzyć projekt w folderze SocketIOProject :

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

    To polecenie tworzy projekt JavaScript. Wejdź do folderu SocketIOProject, aby uruchomić następujące polecenia.

  3. Obecnie pakiet Function Bundle nie zawiera wiązania funkcji Socket.IO, dlatego musisz ręcznie dodać pakiet.

    1. Aby wyeliminować odwołanie do pakietu funkcji, zmodyfikuj plik host.json i usuń następujące wiersze.

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Uruchom polecenie:

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Utwórz funkcję na potrzeby negocjacji. Funkcja negocjacji używana do generowania punktów końcowych i tokenów dla klienta w celu uzyskania dostępu do usługi.

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

    Otwórz plik w src/functions/negotiate.js i zastąp jego zawartość następującym kodem:

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

    Ten krok tworzy funkcję negotiate z wyzwalaczem HTTP i powiązaniem wyjściowym SocketIONegotiation, co oznacza, że można użyć wywołania HTTP, aby wyzwolić funkcję i zwrócić wynik negocjacji generowany przez powiązanie SocketIONegotiation.

  5. Utwórz funkcję do przekazywania komunikatów.

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

    Otwórz plik src/functions/message.js i zastąp ciąg następującym kodem:

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

    To wykorzystuje SocketIOTrigger, aby było wyzwalane komunikatem od klienta Socket.IO, oraz powiązanie SocketIO do rozsyłania komunikatów do przestrzeni nazw.

  6. Utwórz funkcję, która zwraca plik index.html do wyświetlenia.

    1. Utwórz folder public w obszarze src/.

    2. Utwórz plik index.html HTML z następującą zawartością.

      <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. Aby zwrócić stronę HTML, utwórz funkcję i kody kopiowania:

      func new --template "Http Trigger" --name index
      
    4. Otwórz plik src/functions/index.js i zastąp ciąg następującym kodem:

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

Jak uruchomić aplikację lokalnie

Po przygotowaniu kodu wykonaj instrukcje uruchamiania przykładu.

Konfigurowanie usługi Azure Storage dla funkcji platformy Azure

Usługa Azure Functions wymaga, aby konto magazynu działało nawet lokalnie. Wybierz jedną z dwóch następujących opcji:

  • Uruchom bezpłatny emulator Azurite.
  • Użyj usługi Azure Storage. Może to wiązać się z kosztami, jeśli będzie on nadal używany.
  1. Instalowanie Azurite
npm install -g azurite
  1. Uruchom emulator magazynu Azurite:
azurite -l azurite -d azurite\debug.log
  1. Upewnij się, że parametr AzureWebJobsStorage w pliku local.settings.json jest ustawiony na UseDevelopmentStorage=true.

Skonfiguruj usługę Web PubSub dla Socket.IO

  1. Dodaj ciąg połączenia do aplikacji Function App:
func settings add WebPubSubForSocketIOConnectionString "<connection string>"
  1. Dodaj ustawienia huba do usługi Web PubSub dla 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="*"

Ciąg połączenia można uzyskać za pomocą polecenia Azure CLI

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

Dane wyjściowe zawierają primaryConnectionString i secondaryConnectionString, a każdy z nich jest dostępny.

Konfigurowanie tunelu

W trybie serverless usługa używa webhooków do wywoływania funkcji. Gdy próbujesz uruchomić aplikację lokalnie, głównym problemem jest umożliwienie usłudze dostępu do lokalnego punktu końcowego funkcji.

Najprostszym sposobem osiągnięcia tego celu jest użycie narzędzia Tunnel Tool

  1. Zainstaluj narzędzie tunelu:

    npm install -g @azure/web-pubsub-tunnel-tool
    
  2. Uruchamianie tunelu

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

    Jest --upstream to adres URL, który udostępnia lokalna funkcja Azure. Port może być inny i można sprawdzić dane wyjściowe podczas uruchamiania funkcji w następnym kroku.

Uruchamianie przykładowej aplikacji

Po uruchomieniu narzędzia do tunelowania możesz uruchomić aplikację funkcji lokalnie:

func start

Odwiedź stronę internetową pod adresem http://localhost:7071/api/index.

Zrzut ekranu przedstawiający aplikację do czatu bezserwerowego.

Następne kroki

Następnie możesz spróbować użyć aplikacji Bicep do wdrożenia aplikacji w trybie online przy użyciu uwierzytelniania opartego na tożsamościach: