Condividi tramite


Esercitazione: Pubblicare dati nei client Socket.IO in modalità senza server nelle Funzioni di Azure con Python (anteprima)

Questa esercitazione illustra come pubblicare dati in Socket.IO client in modalità serverless in Python creando un'applicazione di indice NASDAQ in tempo reale integrata con funzioni di Azure.

Trovare esempi di codice completi usati in questa esercitazione:

Importante

La modalità predefinita richiede un server persistente, non è possibile integrare Web PubSub per Socket.IO in modalità predefinita con Funzioni di Azure.

Prerequisiti

Creare un Web PubSub per la risorsa Socket.IO in modalità serverless

Per creare un web pubSub per Socket.IO, è possibile usare il comando seguente dell'interfaccia della riga di comando di Azure:

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

Creare un progetto di Funzioni di Azure in locale

È necessario seguire la procedura per avviare un progetto di Funzioni di Azure locale.

  1. Seguire la procedura per installare lo strumento di base di Funzioni di Azure più recente

  2. Nella finestra del terminale o da un prompt dei comandi eseguire il comando seguente per creare un progetto nella cartella SocketIOProject:

    func init SocketIOProject --worker-runtime python
    

    Questo comando crea un progetto funzione basato su Python. Immettere la cartella SocketIOProject per eseguire i comandi seguenti.

  3. Attualmente, il pacchetto di funzioni non include il binding di funzioni Socket.IO, quindi è necessario aggiungere manualmente il pacchetto.

    1. Per eliminare il riferimento al pacchetto di funzioni, modificare il file host.json e rimuovere le righe seguenti.

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

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Sostituire il contenuto in function_app.py con i codici:

    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')
    

    Ecco la spiegazione di queste funzioni:

    • publish_data: questa funzione aggiorna l'indice NASDAQ ogni secondo con una modifica casuale e lo trasmette ai client connessi con Socket.IO binding di output.

    • negotiate: Questa funzione restituisce un risultato della negoziazione al cliente.

    • index: questa funzione restituisce una pagina HTML statica.

    Aggiungere quindi un index.html file

    Creare il file index.html con il contenuto:

    <!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>
    

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

    Prima di tutto negozia con l'app per le funzioni per ottenere l'URI e il percorso al servizio. Registrare un callback per aggiornare l'indice.

Come eseguire l'app in locale

Dopo aver preparato il codice, seguire le istruzioni per eseguire l'esempio.

Configurare Archiviazione di Azure per Funzioni di Azure

Funzioni di Azure richiede che un account di archiviazione possa essere eseguito anche in locale. Scegliere una delle due opzioni seguenti:

  • Eseguire l'emulatore Azurite gratuito.
  • Usare il servizio Archiviazione di Azure. Ciò può comportare costi se si continua a usarlo.
  1. Installare Azurite

    npm install -g azurite
    
  2. Avviare l'emulatore di archiviazione Azurite:

    azurite -l azurite -d azurite\debug.log
    
  3. Assicurarsi che AzureWebJobsStorage in local.settings.json sia impostato su UseDevelopmentStorage=true.

Impostare la configurazione di Web PubSub per Socket.IO

Aggiungere una stringa di connessione all'app per le funzioni:

func settings add WebPubSubForSocketIOConnectionString "<connection string>"

Eseguire un'app di esempio

Dopo l'esecuzione dello strumento tunnel, è possibile eseguire l'app per le funzioni in locale:

func start

Visitare la pagina Web all'indirizzo http://localhost:7071/api/index.

Screenshot dell'app.

Passaggi successivi

Successivamente, è possibile provare a usare Bicep per distribuire l'app online con l'autenticazione basata sull'identità: