Partilhar via


Tutorial: Publicar dados para clientes Socket.IO no modo sem servidor no Azure Função com Python (Pré-visualização)

Este tutorial orienta você sobre como publicar dados para Socket.IO clientes no Modo sem Servidor em Python criando um aplicativo de índice NASDAQ em tempo real integrado ao Azure Function.

Encontre exemplos de código completo que são usados neste tutorial:

Importante

O Modo Padrão precisa de um servidor persistente, não é possível integrar o Web PubSub para Socket.IO no modo padrão com a Função do Azure.

Pré-requisitos

Criar um recurso Web PubSub para Socket.IO no modo sem servidor

Para criar um Web PubSub para Socket.IO, você pode usar o seguinte comando da CLI do Azure:

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

Criar um projeto do Azure Function localmente

Você deve seguir as etapas para iniciar um projeto local do Azure Function.

  1. Siga a etapa para instalar a ferramenta principal mais recente do Azure Function

  2. Na janela do terminal ou em um prompt de comando, execute o seguinte comando para criar um projeto na SocketIOProject pasta:

    func init SocketIOProject --worker-runtime python
    

    Este comando cria um projeto de função baseado em Python. E digite a pasta SocketIOProject para executar os seguintes comandos.

  3. Atualmente, o Pacote de Funções não inclui Vinculação de Função Socket.IO, pelo que é necessário adicionar o pacote manualmente.

    1. Para eliminar a referência do pacote de funções, edite o arquivo host.json e remova as seguintes linhas.

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

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Substitua o conteúdo em function_app.py pelos códigos:

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

    Aqui está a explicação dessas funções:

    • publish_data: Esta função atualiza o índice NASDAQ a cada segundo com uma alteração aleatória e transmite-o para clientes conectados com Socket.IO Output Binding.

    • negotiate: Esta função responde a um resultado de negociação para o cliente.

    • index: Esta função retorna uma página HTML estática.

    Em seguida, adicione um index.html arquivo

    Crie o arquivo index.html com o conteúdo:

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

    A peça-chave no 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);
        });
    }
    

    Primeiro, negoceia com a aplicação de função para obter o URI e o caminho para o serviço. Além disso, registe um retorno de chamada para atualizar o índice.

Como executar o aplicativo localmente

Depois que o código é preparado, siga as instruções para executar o exemplo.

Configurar o Armazenamento do Azure para o Azure Function

O Azure Functions requer uma conta de armazenamento para funcionar mesmo em execução local. Escolha uma das duas opções a seguir:

  • Execute o emulador gratuito do Azurite.
  • Use o serviço de Armazenamento do Azure. Este poderá implicar custos se continuar a utilizá-lo.
  1. Instalar o Azurite

    npm install -g azurite
    
  2. Inicie o emulador de armazenamento Azurite:

    azurite -l azurite -d azurite\debug.log
    
  3. Certifique-se de que o AzureWebJobsStorage in local.settings.json definido como UseDevelopmentStorage=true.

Configurar a configuração do Web PubSub para Socket.IO

Adicione a cadeia de conexão à aplicação de função:

func settings add WebPubSubForSocketIOConnectionString "<connection string>"

Executar aplicativo de exemplo

Depois que a ferramenta de túnel estiver em execução, você poderá executar o aplicativo de função localmente:

func start

E visite a página em http://localhost:7071/api/index.

Captura de ecrã da aplicação.

Próximos passos

Em seguida, você pode tentar usar o Bicep para implantar o aplicativo online com autenticação baseada em identidade: