Partager via


Tutoriel : Publier des données sur Socket.IO clients en mode serverless dans Azure Function avec Python (préversion)

Ce tutoriel vous guide tout au long de la publication de données sur Socket.IO clients en mode serverless en Python en créant une application d’index NASDAQ en temps réel intégrée à Azure Function.

Obtenez les exemples de code complets utilisés dans ce tutoriel :

Important

Le mode par défaut a besoin d’un serveur persistant. Vous ne pouvez pas intégrer Web PubSub pour Socket.IO en mode par défaut avec Azure Functions.

Prerequisites

Créer une ressource Web PubSub pour Socket.IO en mode serverless

Pour créer une ressource web PubSub pour Socket.IO, vous pouvez utiliser la commande Azure CLI suivante :

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

Créer un projet Azure Functions localement

Vous devez suivre les étapes pour lancer un projet Azure Functions local.

  1. Suivez l’étape pour installer la dernière version d’Azure Functions Core Tools.

  2. Dans la fenêtre de terminal ou à partir d’une invite de commandes, exécutez la commande suivante pour créer un projet dans le dossier SocketIOProject :

    func init SocketIOProject --worker-runtime python
    

    Cette commande crée un projet de fonction basé sur Python. Entrez le dossier SocketIOProject pour exécuter les commandes suivantes.

  3. Actuellement, le bundle de fonctions n’inclut pas la liaison de fonction Socket.IO. Vous devez donc ajouter manuellement le package.

    1. Pour éliminer la référence du bundle de fonctions, modifiez le fichier host.json et supprimez les lignes suivantes.

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Exécutez la commande suivante :

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Remplacez le contenu dans function_app.py par les 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')
    

    Voici l’explication de ces fonctions :

    • publish_data : cette fonction met à jour l’index SQQ toutes les secondes avec une modification aléatoire et le diffuse aux clients connectés avec Socket.IO liaison de sortie.

    • negotiate: cette fonction fournit un résultat de négociation au client.

    • index: cette fonction retourne une page HTML statique.

    Ensuite, ajoutez un index.html fichier

    Créez le fichier index.html avec le contenu :

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

    Partie clé dans le 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);
        });
    }
    

    Il négocie d’abord avec la Function App pour obtenir l’URI et le chemin d’accès au service. Puis inscrivez un rappel pour mettre à jour l’index.

Comment exécuter l’application localement

Une fois le code préparé, suivez les instructions pour exécuter l’exemple.

Configurer Stockage Azure pour Azure Functions

Un compte de stockage est nécessaire pour l’utilisation d’Azure Functions, même en cas d’exécution locale. Choisissez l’une des deux options suivantes :

  • Exécutez l’émulateur Azurite gratuit.
  • Utilisez le service Stockage Azure. Cela peut entraîner des coûts si vous continuez à l’utiliser.
  1. Installez l’émulateur Azurite.

    npm install -g azurite
    
  2. Démarrez l’émulateur de stockage Azurite :

    azurite -l azurite -d azurite\debug.log
    
  3. Vérifiez que le AzureWebJobsStorage dans local.settings.json est défini sur UseDevelopmentStorage=true.

Définissez la configuration de Web PubSub pour Socket.IO

Ajoutez une chaîne de connexion à l’application de fonction :

func settings add WebPubSubForSocketIOConnectionString "<connection string>"

Exécuter l’exemple d’application

Une fois que l’outil tunnel est en cours d’exécution, vous pouvez exécuter l’application de fonction localement :

func start

Et accéder à la page web à http://localhost:7071/api/index.

Capture d’écran de l’application.

Étapes suivantes

Ensuite, vous pouvez essayer d’utiliser Bicep pour déployer l’application en ligne avec l’authentification basée sur l’identité :