zoukankan      html  css  js  c++  java
  • 【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)

    问题描述

    使用 python websockets 模块作为Socket的服务端,发布到App Service for Linux环境后,发现Docker Container无法启动。错误消息为:

    2021-10-28T02:39:51.812Z INFO  - docker run -d -p 1764:8000 --name test_0_c348bc62 -e WEBSITE_SITE_NAME=sockettest -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=sockettest.chinacloudsites.cn -e WEBSITE_INSTANCE_ID=08307498aa991c84523184617d17f074bad5139bd2c0710fdf2b1a0ad3d3a9b7 -e HTTP_LOGGING_ENABLED=1 appsvc/python:3.8_20210709.2 python socket_server.py 
    
    2021-10-28T02:39:55.922Z INFO  - Initiating warmup request to container test_0_c348bc62 for site sockettest
    2021-10-28T02:40:11.177Z INFO  - Waiting for response to warmup request for container test_0_c348bc62. Elapsed time = 15.2556084 sec
    ...
    2021-10-28T02:43:33.439Z INFO  - Waiting for response to warmup request for container test_0_c348bc62. Elapsed time = 217.5175373 sec
    2021-10-28T02:43:46.644Z ERROR - Container test_0_c348bc62 for site sockettest did not start within expected time limit. Elapsed time = 230.7221775 sec
    2021-10-28T02:43:46.645Z ERROR - Container test_0_c348bc62 didn't respond to HTTP pings on port: 8000, failing site start. See container logs for debugging.
    2021-10-28T02:43:46.672Z INFO  - Stopping site sockettest because it failed during startup.

    PS:应用上云的需求。

    问题解决

    这是因为App Service Linux使用Container的启动需要Python代码中对HTTP进行正确的响应,否则Site无法启动。而这次的Python代码并不包含对HTTP请求的响应(需要Web框架),所以无法正确启动。

    在Azure App Service for Linux - Python的文档中,主要介绍的两种Web框架为 Flask 和 Django。 接下来,就通过Flask 和SocketIO来实现WebSocket功能。

    实现 Python SocketIO 代码及步骤

    1)创建 app.py 文件,并复制以下内容,作为Socket的服务端及Flask应用的启动

    from flask import Flask, render_template, session, copy_current_request_context
    from flask_socketio import SocketIO, emit, disconnect
    from threading import Lock
    import os
    
    
    async_mode = None
    app = Flask(__name__)
    
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app, async_mode=async_mode)
    thread = None
    thread_lock = Lock()
    
    ## Used by App Service For linux
    PORT = os.environ["PORT"] 
    serverIP = "0.0.0.0"
    
    # # Used by Local debug.
    # PORT = 5000 
    # serverIP = "127.0.0.1"
    
    @app.route('/')
    def index():
        return render_template('index.html', async_mode=socketio.async_mode)
    
    
    @socketio.on('my_event', namespace='/test')
    def test_message(message):
        print('receive message:' + message['data'],)
        session['receive_count'] = session.get('receive_count', 0) + 1
        emit('my_response',
             {'data': message['data'], 'count': session['receive_count']})
    
    
    @socketio.on('my_broadcast_event', namespace='/test')
    def test_broadcast_message(message):
        print('broadcast message:' + message['data'],)
        session['receive_count'] = session.get('receive_count', 0) + 1
        emit('my_response',
             {'data': message['data'], 'count': session['receive_count']},
             broadcast=True)
    
    
    @socketio.on('disconnect_request', namespace='/test')
    def disconnect_request():
        @copy_current_request_context
        def can_disconnect():
            disconnect()
    
        session['receive_count'] = session.get('receive_count', 0) + 1
        emit('my_response',
             {'data': 'Disconnected!', 'count': session['receive_count']},
             callback=can_disconnect)
    
    
    if __name__ == '__main__':
        socketio.run(app,port=PORT, host=serverIP, debug=True)
        print('socket io start')

    2)创建 template/index.html,并复制以下内容,作为Socket的客户端,验证WebSocket的正常工作

    <!DOCTYPE HTML>
    <html>
    <head>
        <title>Socket-Test</title>
        <script src="//code.jquery.com/jquery-1.12.4.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
        <script type="text/javascript" charset="utf-8">
            $(document).ready(function() {
    
                namespace = '/test';
                var socket = io(namespace);
    
                socket.on('connect', function() {
                    socket.emit('my_event', {data: 'connected to the SocketServer...'});
                });
    
                socket.on('my_response', function(msg, cb) {
                    $('#log').append('<br>' + $('<div/>').text('logs #' + msg.count + ': ' + msg.data).html());
                    if (cb)
                        cb();
                });
                $('form#emit').submit(function(event) {
                    socket.emit('my_event', {data: $('#emit_data').val()});
                    return false;
                });
                $('form#broadcast').submit(function(event) {
                    socket.emit('my_broadcast_event', {data: $('#broadcast_data').val()});
                    return false;
                });
                $('form#disconnect').submit(function(event) {
                    socket.emit('disconnect_request');
                    return false;
                });
            });
        </script>
    </head>
    <body style="background-color:white;">
    
        <h1 style="background-color:white;">Socket</h1>
        <form id="emit" method="POST" action='#'>
            <input type="text" name="emit_data" id="emit_data" placeholder="Message">
            <input type="submit" value="Send Message">
        </form>
        <form id="broadcast" method="POST" action='#'>
            <input type="text" name="broadcast_data" id="broadcast_data" placeholder="Message">
            <input type="submit" value="Send Broadcast Message">
        </form>
    
        <form id="disconnect" method="POST" action="#">
            <input type="submit" value="Disconnect Server">
        </form>
        <h2 style="background-color:white;">Logs</h2>
        <div id="log" ></div>
    </body>
    </html>

    3)创建 requirements.txt 文件,并包含以下module及版本,如果版本不适合,可以适当修改。

    Flask==1.0.2
    Flask-Login==0.4.1
    Flask-Session==0.3.1
    itsdangerous==1.1.0
    Jinja2==2.10
    MarkupSafe==1.1.0
    six==1.11.0
    Werkzeug==0.14.1
    Flask-SocketIO==4.3.1
    python-engineio==3.13.2
    python-socketio==4.6.0
    eventlet==0.30.2

    以上三个就是整个项目的源文件,VS Code中的文件结构为:

    4)在VS Code中使用az webapp up来部署Python Web应用

    #设置登录环境为中国区Azure
    az cloud set -n AzureChinaCloud
    az login
    
    #部署代码,如果pythonlinuxwebsocket01不存在,则自动创建定价层位B1的App Service
    az webapp up --sku B1 --name pythonlinuxwebsocket01

    效果展示:

     

    5)修改App Service的启动命令

    gunicorn --worker-class eventlet -w 1 app:app

    注:为了避免 flask-socketIO 服务器部署 400 Bad Request 问题,所以需要使用 eventlet 作为工作进程。详细说明可见:https://blog.csdn.net/weixin_43958804/article/details/109024348

    6)  开启WebSocket, 启用HTTP,  设置PORT参数

    注:修改后,重启App Service。如果重启后使用HTTP请求,但是发生了302跳转到HTTPS的情况,就可以考虑重新部署一次站点。使用第四步方法,az webapp up然container重新生成项目信息。

    7)验证Web Socket

    使用HTTP访问刚刚部署的App Service URL。 

     

    附录一:解决flask-socketIO 服务器部署 400 Bad Request 问题

    使用eventlet,设置启动命令:gunicorn --worker-class eventlet -w 1 app:app

    附录二:Gunicorn ImportError: cannot import name 'ALREADY_HANDLED' from 'eventlet.wsgi' in docker

    Installing older version of eventlet solved the problem: pip install eventlet==0.30.2

    参考资料:

    Implement a WebSocket Using Flask and Socket-IO(Python): https://medium.com/swlh/implement-a-websocket-using-flask-and-socket-io-python-76afa5bbeae1

    解决flask-socketIO 服务器部署 400 Bad Request 问题:https://blog.csdn.net/weixin_43958804/article/details/109024348

     

    当在复杂的环境中面临问题,格物之道需:浊而静之徐清,安以动之徐生。 云中,恰是如此!

  • 相关阅读:
    计算机硬件知识整理
    cf689d ST表RMQ+二分
    hdu5289 ST表+二分
    hdu5443 ST表裸题:求区间最大
    poj3264 倍增法(ST表)裸题
    cf932d 树上倍增
    zoj3195 联通树上三个点的路径长
    hdu6107 倍增法st表
    hdu2586 lca倍增法
    poj1470 LCA倍增法
  • 原文地址:https://www.cnblogs.com/lulight/p/15501015.html
Copyright © 2011-2022 走看看