【Azure 應用服務】Azure App Service For Linux 上實現 Python Flask Web Socket 專案 Http/Https

路邊兩盞燈發表於2021-12-15

問題描述

在上篇博文“【Azure 應用服務】App Service for Linux 中實現 WebSocket 功能 (Python SocketIO)”中,實現了通過 HTTP 方式訪問部署在Azure App Service For Linux上的Python Flask Web Socket專案, 但是當使用HTTPS訪問時候,socket.io所傳送的GET請求都能正常。

HTTP 成功 HTTPS 失敗
【Azure 應用服務】Azure App Service For Linux 上實現 Python Flask Web Socket 專案 Http/Https

 

【Azure 應用服務】Azure App Service For Linux 上實現 Python Flask Web Socket 專案 Http/Https

 

但是POST請求全部返回400 Bad Request

【Azure 應用服務】Azure App Service For Linux 上實現 Python Flask Web Socket 專案 Http/Https

 

 

那麼,如何來解決並實現HTTPS呢?

 

問題解決

使用 eventlet.monkey_patch() : 猴子補丁,在執行時動態修改已有的程式碼,而不需要修改原始程式碼。對應 “模組執行時替換的功能” 來進行理解。

  1. Monkey patch就是在執行時對已有的程式碼進行修改,達到hot patch的目的。
  2. 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

 

測試效果

【Azure 應用服務】Azure App Service For Linux 上實現 Python Flask Web Socket 專案 Http/Https

 

 

遇見的問題

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

 

相關文章