【Azure|【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 osasync_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的正常工作
Socket-Test - 锐客网 Socket
Logs


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中的文件结构为:
【Azure|【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
文章图片



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

效果展示:
【Azure|【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
文章图片


5)修改App Service的启动命令
gunicorn --worker-class eventlet -w 1 app:app

【Azure|【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
文章图片

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

6)开启WebSocket, 启用HTTP,设置PORT参数
【Azure|【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
文章图片

注:修改后,重启App Service。如果重启后使用HTTP请求,但是发生了302跳转到HTTPS的情况,就可以考虑重新部署依次站点。使用第四步方法,az webapp up然container重新生成项目信息。
7)验证Web Socket
使用HTTP访问刚刚部署的App Service URL。
【Azure|【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
文章图片

【【Azure|【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)】


附录一:解决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

    推荐阅读