問題描述
在上篇博文“【Azure 應用服務】App Service for Linux 中實現 WebSocket 功能 (Python SocketIO)”中,實現了通過 HTTP 方式訪問部署在Azure App Service For Linux上的Python Flask Web Socket專案, 但是當使用HTTPS訪問時候,socket.io所傳送的GET請求都能正常。
HTTP 成功 | HTTPS 失敗 |
|
但是POST請求全部返回400 Bad Request
那麼,如何來解決並實現HTTPS呢?
問題解決
使用 eventlet.monkey_patch() : 猴子補丁,在執行時動態修改已有的程式碼,而不需要修改原始程式碼。對應 “模組執行時替換的功能” 來進行理解。
- Monkey patch就是在執行時對已有的程式碼進行修改,達到hot patch的目的。
- Eventlet中大量使用了該技巧,以替換標準庫中的元件,比如socket。
在建立socketio物件時候,指定使用eventlet模組,然後設定cors_allowed_origins為*。
socketio = SocketIO(app, async_mode="eventlet",cors_allowed_origins='*')
一個簡單的Socket Test專案(服務端+客戶端)例項程式碼如下:
pythonsockettest(Folder Name) ----templates --------index.html ----app.py ----requirements.txt
Templates/Index.html
<!DOCTYPE HTML> <html> <head> <title>Socket-Test</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.min.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>
app.py
import eventlet eventlet.monkey_patch() 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!' ## For http #socketio = SocketIO(app, async_mode=async_mode) ## For https socketio = SocketIO(app, async_mode="eventlet",cors_allowed_origins='*') 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')
requirements.txt
Flask==2.0.2
Flask-SocketIO==5.1.1
eventlet==0.30.2
部署在Azure App Service後,需要設定啟動命令:
gunicorn --bind=0.0.0.0 --timeout 600 --worker-class "eventlet" app:app
配置方法可見:https://www.cnblogs.com/lulight/p/15501015.html (第五段:修改App Service的啟動命令)
附錄:部署上Azure App的程式碼
#設定登入環境為中國區Azure az cloud set -n AzureChinaCloud az login #部署程式碼,如果pythonlinuxwebsocket01不存在,則自動建立定價層位B1的App Service az webapp up --sku B1 --name pythonlinuxwebsocket01
測試效果
遇見的問題
1: eventlet worker requires eventlet 0.24.1 or higher
2021-12-14T03:31:30.581051185Z Error: class uri 'eventlet' invalid or not found: 2021-12-14T03:31:30.581056185Z 2021-12-14T03:31:30.581059786Z [Traceback (most recent call last): 2021-12-14T03:31:30.581063086Z File "/opt/python/3.7.9/lib/python3.7/site-packages/gunicorn/workers/geventlet.py", line 10, in <module> 2021-12-14T03:31:30.581067386Z import eventlet 2021-12-14T03:31:30.581070686Z ModuleNotFoundError: No module named 'eventlet' 2021-12-14T03:31:30.581073986Z 2021-12-14T03:31:30.581077187Z During handling of the above exception, another exception occurred: 2021-12-14T03:31:30.581081587Z 2021-12-14T03:31:30.581084787Z Traceback (most recent call last): 2021-12-14T03:31:30.581088187Z File "/opt/python/3.7.9/lib/python3.7/site-packages/gunicorn/util.py", line 99, in load_class 2021-12-14T03:31:30.581107988Z mod = importlib.import_module('.'.join(components)) 2021-12-14T03:31:30.581111389Z File "/opt/python/3.7.9/lib/python3.7/importlib/__init__.py", line 127, in import_module 2021-12-14T03:31:30.581114489Z return _bootstrap._gcd_import(name[level:], package, level) 2021-12-14T03:31:30.581117589Z File "<frozen importlib._bootstrap>", line 1006, in _gcd_import 2021-12-14T03:31:30.581120689Z File "<frozen importlib._bootstrap>", line 983, in _find_and_load 2021-12-14T03:31:30.581123789Z File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked 2021-12-14T03:31:30.581126890Z File "<frozen importlib._bootstrap>", line 677, in _load_unlocked 2021-12-14T03:31:30.581130090Z File "<frozen importlib._bootstrap_external>", line 728, in exec_module 2021-12-14T03:31:30.581133190Z File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed 2021-12-14T03:31:30.581136290Z File "/opt/python/3.7.9/lib/python3.7/site-packages/gunicorn/workers/geventlet.py", line 12, in <module> 2021-12-14T03:31:30.581139490Z raise RuntimeError("eventlet worker requires eventlet 0.24.1 or higher") 2021-12-14T03:31:30.581142690Z RuntimeError: eventlet worker requires eventlet 0.24.1 or higher
2:cannot import name 'ALREADY_HANDLED' from 'eventlet.wsgi'
2021-12-14T05:14:28.142182566Z Error: class uri 'eventlet' invalid or not found: 2021-12-14T05:14:28.142189566Z 2021-12-14T05:14:28.142194867Z [Traceback (most recent call last): 2021-12-14T05:14:28.142199767Z File "/opt/python/3.7.9/lib/python3.7/site-packages/gunicorn/util.py", line 99, in load_class 2021-12-14T05:14:28.142212168Z mod = importlib.import_module('.'.join(components)) 2021-12-14T05:14:28.142217368Z File "/opt/python/3.7.9/lib/python3.7/importlib/__init__.py", line 127, in import_module 2021-12-14T05:14:28.142222569Z return _bootstrap._gcd_import(name[level:], package, level) 2021-12-14T05:14:28.142239170Z File "<frozen importlib._bootstrap>", line 1006, in _gcd_import 2021-12-14T05:14:28.142244870Z File "<frozen importlib._bootstrap>", line 983, in _find_and_load 2021-12-14T05:14:28.142249471Z File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked 2021-12-14T05:14:28.142254171Z File "<frozen importlib._bootstrap>", line 677, in _load_unlocked 2021-12-14T05:14:28.142258771Z File "<frozen importlib._bootstrap_external>", line 728, in exec_module 2021-12-14T05:14:28.142263371Z File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed 2021-12-14T05:14:28.142268172Z File "/opt/python/3.7.9/lib/python3.7/site-packages/gunicorn/workers/geventlet.py", line 20, in <module> 2021-12-14T05:14:28.142272972Z from eventlet.wsgi import ALREADY_HANDLED as EVENTLET_ALREADY_HANDLED 2021-12-14T05:14:28.142277472Z ImportError: cannot import name 'ALREADY_HANDLED' from 'eventlet.wsgi' (/tmp/8d9bebfefe8421c/antenv/lib/python3.7/site-packages/eventlet/wsgi.py) 2021-12-14T05:14:28.142282173Z ]
以上兩個問題都是通過修改 requirements.txt 中 eventlet==0.30.2 後,重新部署。問題解決。
參考資料
App Service for Linux 中實現 WebSocket 功能 (Python SocketIO) : https://www.cnblogs.com/lulight/p/15501015.html