zoukankan      html  css  js  c++  java
  • 【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https

    问题描述

    在上篇博文“【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() : 猴子补丁,在运行时动态修改已有的代码,而不需要修改原始代码。对应 “模块运行时替换的功能” 来进行理解。

    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

    测试效果

    遇见的问题

    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

    当在复杂的环境中面临问题,格物之道需:浊而静之徐清,安以动之徐生。 云中,恰是如此!

  • 相关阅读:
    【[Offer收割]编程练习赛12 B】一面砖墙
    【[Offer收割]编程练习赛12 A】歌德巴赫猜想
    【codeforces 779E】Bitwise Formula
    Java Web整合开发(85)
    数字
    T2602 最短路径问题 codevs
    P3378 堆【模板】 洛谷
    T1013 求先序排列 codevs
    P1717 钓鱼 洛谷
    P2085 最小函数值 洛谷
  • 原文地址:https://www.cnblogs.com/lulight/p/15693764.html
Copyright © 2011-2022 走看看