다음을 통해 공유


자습서: Python을 사용하여 Azure Function에서 서버리스 모드로 Socket.IO 클라이언트에 데이터 게시(미리 보기)

이 자습서에서는 Azure Function과 통합된 실시간 NASDAQ 인덱스 애플리케이션을 만들어 Python의 서버리스 모드에서 Socket.IO 클라이언트에 데이터를 게시하는 방법을 안내합니다.

이 자습서에서 사용되는 전체 코드 샘플을 찾습니다.

중요합니다

기본 모드에는 영구 서버가 필요하며, Azure Function과 기본 모드에서 Socket.IO Web PubSub를 통합할 수 없습니다.

필수 조건

서버리스 모드에서 Socket.IO 리소스용 Web PubSub 만들기

Socket.IO Web PubSub를 만들려면 다음 Azure CLI 명령을 사용할 수 있습니다.

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

로컬로 Azure Function 프로젝트 만들기

단계에 따라 로컬 Azure Function 프로젝트를 시작해야 합니다.

  1. 단계에 따라 최신 Azure Function Core 도구를 설치합니다.

  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: 이 함수는 임의 변경으로 1초마다 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 Function용 Azure Storage 설정

Azure Functions를 사용하려면 로컬에서 실행되더라도 스토리지 계정이 필요합니다. 다음 두 옵션 중 하나를 선택합니다.

  1. Azurite 설치

    npm install -g azurite
    
  2. Azurite 스토리지 에뮬레이터를 시작합니다.

    azurite -l azurite -d azurite\debug.log
    
  3. AzureWebJobsStoragelocal.settings.jsonUseDevelopmentStorage=true로 설정되어 있는지 확인합니다.

Socket.IO Web PubSub 구성 설정

함수 APP에 연결 문자열을 추가합니다.

func settings add WebPubSubForSocketIOConnectionString "<connection string>"

샘플 앱 실행

터널 도구가 실행되면 함수 앱을 로컬로 실행할 수 있습니다.

func start

http://localhost:7071/api/index 웹 페이지를 방문하십시오.

앱의 스크린샷.

다음 단계

다음으로, Bicep을 사용하여 ID 기반 인증을 사용하여 온라인으로 앱을 배포할 수 있습니다.