問題描述
使用 python websockets 模塊作為Socket的服務(wù)端恢共,發(fā)布到App Service for Linux環(huán)境后,發(fā)現(xiàn)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:應(yīng)用上云的需求。
問題解決
這是因為App Service Linux使用Container的啟動需要Python代碼中對HTTP進(jìn)行正確的響應(yīng)对室,否則Site無法啟動拉宗。而這次的Python代碼并不包含對HTTP請求的響應(yīng)(需要Web框架),所以無法正確啟動渡冻。
在Azure App Service for Linux - Python的文檔中戚扳,主要介紹的兩種Web框架為 Flask 和 Django。 接下來族吻,就通過Flask 和SocketIO來實現(xiàn)WebSocket功能帽借。
實現(xiàn) Python SocketIO 代碼及步驟
1)創(chuàng)建 app.py 文件,并復(fù)制以下內(nèi)容超歌,作為Socket的服務(wù)端及Flask應(yīng)用的啟動
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)創(chuàng)建 template/index.html砍艾,并復(fù)制以下內(nèi)容,作為Socket的客戶端巍举,驗證WebSocket的正常工作
<!DOCTYPE HTML>
<html>
<head>
<title>Socket-Test</title>
<script src="http://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="http://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)創(chuàng)建 requirements.txt 文件辐董,并包含以下module及版本,如果版本不適合禀综,可以適當(dāng)修改。
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中的文件結(jié)構(gòu)為:
4)在VS Code中使用az webapp up來部署Python Web應(yīng)用
#設(shè)置登錄環(huán)境為中國區(qū)Azure
az cloud set -n AzureChinaCloud
az login
#部署代碼定枷,如果pythonlinuxwebsocket01不存在,則自動創(chuàng)建定價層位B1的App Service
az webapp up --sku B1 --name pythonlinuxwebsocket01
效果展示:
5)修改App Service的啟動命令
gunicorn --worker-class eventlet -w 1 app:app
注:為了避免 flask-socketIO 服務(wù)器部署 400 Bad Request 問題届氢,所以需要使用 eventlet 作為工作進(jìn)程欠窒。詳細(xì)說明可見:https://blog.csdn.net/weixin_43958804/article/details/109024348
6) 開啟WebSocket, 啟用HTTP, 設(shè)置PORT參數(shù)
注:修改后退子,重啟App Service岖妄。如果重啟后使用HTTP請求,但是發(fā)生了302跳轉(zhuǎn)到HTTPS的情況寂祥,就可以考慮重新部署一次站點荐虐。使用第四步方法,az webapp up然container重新生成項目信息丸凭。
7)驗證Web Socket
使用HTTP訪問剛剛部署的App Service URL福扬。
附錄一:解決flask-socketIO 服務(wù)器部署 400 Bad Request 問題
使用eventlet,設(shè)置啟動命令: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 服務(wù)器部署 400 Bad Request 問題:https://blog.csdn.net/weixin_43958804/article/details/109024348
當(dāng)在復(fù)雜的環(huán)境中面臨問題惜犀,格物之道需:濁而靜之徐清铛碑,安以動之徐生。 云中虽界,恰是如此!
標(biāo)簽: App Service, Azure Developer, azure python, 云中實現(xiàn)web socket