问题描述
使用 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.py2021-10-28T02:39:55.922Z INFO - Initiating warmup request to container test_0_c348bc62 for site sockettest2021-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 sec2021-10-28T02:43:46.644Z ERROR - Container test_0_c348bc62 for site sockettest did not start within expected time limit. Elapsed time = 230.7221775 sec2021-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功能。
实现 Pythonad8SocketIO 代码及步骤
1)创建 app.py 文件,并复制以下内容,作为Socket的服务端及Flask应用的启动
from flask import Flask, render_template, session, copy_current_request_contextfrom flask_socketio import SocketIO, emit, disconnectfrom threading import Lockimport osasync_mode = Noneapp = Flask(__name__)app.config[\'SECRET_KEY\'] = \'secret!\'socketio = SocketIO(app, async_mode=async_mode)thread = Nonethread_lock = Lock()## Used by App Service For linuxPORT = 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) + 1emit(\'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) + 1emit(\'my_response\',{\'data\': message[\'data\'], \'count\': session[\'receive_count\']},broadcast=True)@socketio.on(\'disconnect_request\', namespace=\'/test\')def disconnect_request():@copy_current_request_contextdef can_disconnect():disconnect()session[\'receive_count\'] = session.get(\'receive_count\', 0) + 1emit(\'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\" valad8ue=\"Disconnect Server\"></form><h2 style=\"background-color:white;\">Logs</h2><div id=\"log\" ></div></body></html>
3)创建requirements.txt 文件,并包含以下module及版本,如果版本不适合,可以适当修改。
Flask==1.0.2Flask-Login==0.4.1Flask-Session==0.3.1itsdangerous==1.1.0Jinja2==2.10MarkupSafe==1.1.0six==1.11.0Werkzeug==0.14.1Flask-SocketIO==4.3.1python-engineio==3.13.2python-socketio==4.6.0eventlet==0.30.2
以上三个就是整个项目的源文件,VS Code中的文件结构为:
4)在VS Code中使用az webapp up来部署Python Web应用
#设置登录环境为中国区Azureaz cloud set -n AzureChinaCloudaz login#部署代码,如果pythonlinuxwebsocket01不存在,则自动创建定价层位B1的App Serviceaz 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