Поделиться через


Руководство: Публикация данных клиентам Socket.IO в функции Azure в режиме без сервера с помощью Python (предварительная версия)

В этом руководстве описано, как публиковать данные для Socket.IO клиентов в бессерверном режиме в Python, создавая приложение индекса NASDAQ в режиме реального времени, интегрированное с функцией Azure.

Найдите полные примеры кода, используемые в этом руководстве:

Это важно

В режиме по умолчанию требуется постоянный сервер, вы не можете интегрировать Web PubSub для Socket.IO в режиме по умолчанию с функцией Azure.

Предпосылки

Создание ресурса Web PubSub для Socket.IO в режиме без сервера

Чтобы создать web PubSub для Socket.IO, можно использовать следующую команду Azure CLI :

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

Создание проекта функции Azure локально

Выполните действия, чтобы инициировать локальный проект функции Azure.

  1. Следуйте шагу, чтобы установить последнюю версию основного инструмента Azure Functions

  2. В окне терминала или из командной строки выполните следующую команду, чтобы создать проект в папке SocketIOProject :

    func init SocketIOProject --worker-runtime python
    

    Эта команда создает проект функции на основе Python. Введите папку SocketIOProject , чтобы выполнить следующие команды.

  3. В настоящее время пакет функций не включает привязку функции Socket.IO, поэтому необходимо вручную добавить пакет.

    1. Чтобы исключить ссылку на пакет функций, измените файл host.json и удалите следующие строки.

      "extensionBundle": {
          "id": "Microsoft.Azure.Functions.ExtensionBundle",
          "version": "[4.*, 5.0.0)"
      }
      
    2. Выполните команду:

      func extensions install -p Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO -v 1.0.0-beta.4
      
  4. Замените содержимое function_app.py кодами:

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

    Ниже приведено описание этих функций:

    • publish_data: эта функция обновляет индекс NASDAQ каждую секунду случайным изменением и передает его подключенным клиентам с Socket.IO выходной привязкой.

    • negotiate: Эта функция передает клиенту результат согласования.

    • index: эта функция возвращает статическую HTML-страницу.

    Затем добавьте index.html файл

    Создайте файл index.html с содержимым:

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

    Ключевая часть в 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);
        });
    }
    

    Сначала он проводит переговоры с функциональным приложением, чтобы получить универсальный код ресурса (URI) и путь к службе. И зарегистрируйте обратный вызов для обновления индекса.

Как локально запустить приложение

После подготовки кода выполните инструкции по выполнению примера.

Настройка службы хранилища Azure для функции Azure

Функции Azure требуют, чтобы учетная запись хранения работала даже в локальной среде. Выберите один из двух следующих вариантов:

  • Запустите бесплатный эмулятор Azurite.
  • Используйте службу хранилища Azure. Это может привести к затратам, если вы продолжаете использовать его.
  1. Установка Azurite

    npm install -g azurite
    
  2. Запустите эмулятор хранилища Azurite:

    azurite -l azurite -d azurite\debug.log
    
  3. Убедитесь, что в AzureWebJobsStorage значение установлено на UseDevelopmentStorage=true.

Настройка конфигурации Web PubSub для Socket.IO

Добавьте строку подключения в приложение-функцию.

func settings add WebPubSubForSocketIOConnectionString "<connection string>"

Запуск примера приложения

После запуска средства туннеля можно локально запустить функциональное приложение:

func start

И посетите веб-страницу по адресу http://localhost:7071/api/index.

Снимок экрана: приложение.

Дальнейшие шаги

Затем можно попытаться использовать Bicep для развертывания приложения в Сети с проверкой подлинности на основе удостоверений: