Freigeben über


Anleitung: Veröffentlichen von Daten an Socket.IO-Clients im serverlosen Modus in Azure-Funktion mit Python (Vorschau)

In diesem Lernprogramm erfahren Sie, wie Sie Daten in Socket.IO Clients im Serverless Mode in Python veröffentlichen, indem Sie eine in Azure Function integrierte NASDAQ-Indexanwendung in Echtzeit erstellen.

Finden Sie vollständige Codebeispiele, die in diesem Tutorial verwendet werden:

Von Bedeutung

Der Standardmodus benötigt einen beständigen Server. Web PubSub für Socket.IO kann nicht im Standardmodus mit Azure Function integriert werden.

Voraussetzungen

Erstellen einer Web PubSub für Socket.IO-Ressource im serverlosen Modus

Um ein Web PubSub für Socket.IO zu erstellen, können Sie den folgenden Azure CLI-Befehl verwenden:

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

Lokales Erstellen eines Azure Function-Projekts

Führen Sie die Schritte aus, um ein lokales Azure Function-Projekt zu initiieren.

  1. Sie sollten die Schritte ausführen, um das neueste Azure Function Core Tool zu installieren

  2. Führen Sie im Terminalfenster oder an einer Eingabeaufforderung den folgenden Befehl aus, um ein Projekt im Ordner SocketIOProject zu erstellen:

    func init SocketIOProject --worker-runtime python
    

    Mit diesem Befehl wird ein Python-basiertes Funktionsprojekt erstellt. Und geben Sie den Ordner SocketIOProject ein, um die folgenden Befehle auszuführen.

  3. Derzeit enthält das Function-Bundle nicht die Function-Bindung für Socket.IO, daher müssen Sie das Paket manuell hinzufügen.

    1. Um den Function-Bundle-Verweis zu beseitigen, bearbeiten Sie die host.json-Datei, und entfernen Sie die folgenden Zeilen.

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Führen Sie den folgenden Befehl aus:

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Ersetzen Sie den Inhalt in function_app.py durch die Codes:

    import random
    import azure.functions as func
    from azure.functions.decorators.core import DataType
    from azure.functions import Context
    import json
    
    app = func.FunctionApp()
    current_index= 14000
    
    @app.timer_trigger(schedule="* * * * * *", arg_name="myTimer", run_on_startup=False,
                use_monitor=False)
    @app.generic_output_binding("sio", type="socketio", data_type=DataType.STRING, hub="hub")
    def publish_data(myTimer: func.TimerRequest,
                    sio: func.Out[str]) -> None:
        change = round(random.uniform(-10, 10), 2)
        global current_index
        current_index = current_index + change
        sio.set(json.dumps({
            'actionName': 'sendToNamespace',
            'namespace': '/',
            'eventName': 'update',
            'parameters': [
                current_index
            ]
        }))
    
    @app.function_name(name="negotiate")
    @app.route(auth_level=func.AuthLevel.ANONYMOUS)
    @app.generic_input_binding("negotiationResult", type="socketionegotiation", hub="hub")
    def negotiate(req: func.HttpRequest, negotiationResult) -> func.HttpResponse:
        return func.HttpResponse(negotiationResult)
    
    @app.function_name(name="index")
    @app.route(auth_level=func.AuthLevel.ANONYMOUS)
    def index(req: func.HttpRequest) -> func.HttpResponse:
        path = './index.html'
        with open(path, 'rb') as f:
            return func.HttpResponse(f.read(), mimetype='text/html')
    

    Hier ist die Erläuterung dieser Funktionen:

    • publish_data: Diese Funktion aktualisiert den NASDAQ-Index jede Sekunde mit einer zufälligen Änderung und sendet ihn an verbundene Clients mit Socket.IO Output Binding.

    • negotiate: Diese Funktion antworte auf ein Aushandlungsergebnis an den Client.

    • index: Diese Funktion gibt eine statische HTML-Seite zurück.

    Fügen Sie dann eine index.html Datei hinzu.

    Erstellen Sie die index.html Datei mit dem Inhalt:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Nasdaq Index</title>
        <style>
            /* Reset some default styles */
            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
            }
    
            body {
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
                height: 100vh;
                display: flex;
                justify-content: center;
                align-items: center;
            }
    
            .container {
                background-color: white;
                padding: 40px;
                border-radius: 12px;
                box-shadow: 0 4px 6px rgba(0,0,0,0.1);
                text-align: center;
                max-width: 300px;
                width: 100%;
            }
    
            .nasdaq-title {
                font-size: 2em;
                color: #003087;
                margin-bottom: 20px;
            }
    
            .index-value {
                font-size: 3em;
                color: #16a34a;
                margin-bottom: 30px;
                transition: color 0.3s ease;
            }
    
            .update-button {
                padding: 10px 20px;
                font-size: 1em;
                color: white;
                background-color: #003087;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                transition: background-color 0.3s ease;
            }
    
            .update-button:hover {
                background-color: #002070;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="nasdaq-title">STOCK INDEX</div>
            <div id="nasdaqIndex" class="index-value">14,000.00</div>
        </div>
    
        <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
        <script>
            function updateIndexCore(newIndex) {
                newIndex = parseFloat(newIndex);
                currentIndex = parseFloat(document.getElementById('nasdaqIndex').innerText.replace(/,/g, ''))
                change = newIndex - currentIndex;
                // Update the index value in the DOM
                document.getElementById('nasdaqIndex').innerText = newIndex.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
    
                // Optionally, change the color based on increase or decrease
                const indexElement = document.getElementById('nasdaqIndex');
                if (change > 0) {
                    indexElement.style.color = '#16a34a'; // Green for increase
                } else if (change < 0) {
                    indexElement.style.color = '#dc2626'; // Red for decrease
                } else {
                    indexElement.style.color = '#16a34a'; // Neutral color
                }
            }
    
            async function init() {
                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('update', (index) => {
                    updateIndexCore(index);
                });
            }
    
            init();
        </script>
    </body>
    </html>
    

    Der Schlüsselelement in index.html:

    async function init() {
        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('update', (index) => {
            updateIndexCore(index);
        });
    }
    

    Er handelt zunächst mit der Funktions-App aus, um den URI und den Pfad zum Dienst abzurufen. Und registrieren Sie einen Rückruf, um den Index zu aktualisieren.

Lokales Ausführen der App

Nachdem Code vorbereitet wurde, folgen Sie den Anweisungen zum Ausführen des Beispiels.

Einrichten von Azure Storage für Azure Function

Für Azure Functions ist ein Speicherkonto erforderlich, auch bei lokalen Ausführungen. Wählen Sie eine der beiden folgenden Optionen aus:

  • Führen Sie den kostenlosen Azurite-Emulator aus.
  • Verwenden des Azure Storage-Diensts. Dadurch können Kosten anfallen, wenn Sie ihn weiterhin verwenden.
  1. Installieren Sie den Azurite-Emulator

    npm install -g azurite
    
  2. Starten Sie den Azurite-Speicheremulator:

    azurite -l azurite -d azurite\debug.log
    
  3. Stellen Sie sicher, dass AzureWebJobsStorage in local.settings.json auf UseDevelopmentStorage=true festgelegt ist.

Einrichten der Konfiguration von Web PubSub für Socket.IO

Fügen Sie der Funktions-APP eine Verbindungszeichenfolge hinzu:

func settings add WebPubSubForSocketIOConnectionString "<connection string>"

Ausführen einer Beispiel-App

Nachdem das Tunneltool ausgeführt wurde, können Sie die Funktions-App lokal ausführen:

func start

Und besuchen Sie die Webseite unter http://localhost:7071/api/index.

Screenshot der App.

Nächste Schritte

Als Nächstes können Sie versuchen, Bicep zu verwenden, um die App online mit identitätsbasierter Authentifizierung bereitzustellen: