使用flask和flask-socketio配置nginx、uwsgi

Posted

技术标签:

【中文标题】使用flask和flask-socketio配置nginx、uwsgi【英文标题】:Configure nginx, uwsgi with flask and flask-socketio 【发布时间】:2019-09-27 15:27:25 【问题描述】:

我编写了一个使用 flask-socketio 的 Flask 应用程序。我在端口 8000 上运行烧瓶应用程序,在端口 3000 (react-webpack) 上分别运行客户端应用程序。它在开发模式下完美运行(Flask 中提供的 Web 服务器)。但是,当尝试使用 uwsgi 运行时,我遇到了问题。下面将详细介绍这些问题和配置。

wsgi.py(保持不变)

from cloud_app import app, sock
if __name__ == "__main__":
    sock.run(app,host='0.0.0.0', debug=True, port=8000)

__init__.py(保持不变)

from flask import Flask
from flask_socketio import SocketIO
import secrets

app = Flask(__name__, static_url_path='/static')
app.secret_key = secrets.secret_key
sock = SocketIO(app)

from cloud_app import routes

routes.py(保持不变,明显移除实际逻辑)

...
from flask_cors import CORS
cors = CORS(app, resources=r"/*": "origins": "*", headers=['Content-Type'], expose_headers=['Access-Control-Allow-Origin'], supports_credentials=True)

@app.route('/example')
def example():
    return 'example'

@sock.on('connect', namespace='/example')
def handle_example_connect():
    sock.emit('example', 'Connected!\nAwaiting commands...\n', namespace='/example')
...

第一次配置

取自documentation for flask-socketio and uwsgi,翻译成ini文件

[uwsgi]
module = wsgi:app

master = true
processes = 5
buffer-size=32768
http-websockets = true

http = :8000
gevent = 1000

这里不需要 nginx 配置,因为 webpack 提供了这个,ini 文件被配置为直接响应 http 请求 'http=:port'

CONSOLE:这有时会显示它正在连接“已连接! 等待来自 routes.py 中的 connect 事件的命令...' 但是它也会给出以下错误

POST http://localhost:8000/socket.io/?EIO=3&transport=polling&t=MgTjSL-&sid=5bf4758a09034805b1213fec92620e39 400 (BAD REQUEST)
GET http://localhost:8000/socket.io/?EIO=3&transport=polling&t=MgTjSMG&sid=5bf4758a09034805b1213fec92620e39 400 (BAD REQUEST)
websocket.js:112 WebSocket connection to 'ws://localhost:8000/socket.io/?EIO=3&transport=websocket&sid=5bf4758a09034805b1213fec92620e39' failed: Error during WebSocket handshake: Unexpected response code: 400

UWSGI 进程输出:

...
[pid: 9402|app: 0|req: 16/33] 127.0.0.1 () 44 vars in 1316 bytes [Thu May  9 13:55:41 2019] POST /socket.io/?EIO=3&transport=polling&t=MgTl93y&sid=b208e874c0e64330bdde35ae1773b4e0 => generated 2 bytes in 0 msecs (HTTP/1.1 200) 3 headers in 137 bytes (3 switches on core 996)
[pid: 9402|app: 0|req: 17/34] 127.0.0.1 () 40 vars in 1255 bytes [Thu May  9 13:55:41 2019] GET /socket.io/?EIO=3&transport=polling&t=MgTl94Q&sid=b208e874c0e64330bdde35ae1773b4e0 => generated 12 bytes in 0 msecs (HTTP/1.1 200) 3 headers in 151 bytes (3 switches on core 996)
...
[pid: 9402|app: 0|req: 27/48] 127.0.0.1 () 44 vars in 1316 bytes [Thu May  9 13:56:57 2019] POST /socket.io/?EIO=3&transport=polling&t=MgTlRbG&sid=5c4c38f18f6b47798978440edd181512 => generated 2 bytes in 0 msecs (HTTP/1.1 200) 3 headers in 137 bytes (3 switches on core 998)
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python2.7/dist-packages/flask_socketio/__init__.py", line 43, in __call__
    start_response)
  File "/usr/local/lib/python2.7/dist-packages/engineio/middleware.py", line 47, in __call__
    return self.engineio_app.handle_request(environ, start_response)
  File "/usr/local/lib/python2.7/dist-packages/socketio/server.py", line 360, in handle_request
    return self.eio.handle_request(environ, start_response)
  File "/usr/local/lib/python2.7/dist-packages/engineio/server.py", line 322, in handle_request
    start_response(r['status'], r['headers'] + cors_headers)
IOError: headers already sent

...

第二次配置

取自this question。 ini 文件

[uwsgi]
module = wsgi:app

master = true
processes = 5
buffer-size=32768
http-websockets = true

socket = example_app.sock
chmod-socket = 666

vaccum = true
die-on-term = true

nginx 服务器

server 
    listen 8000;
    location /
        include uwsgi_params;
        uwsgi_pass unix:/path/to/app/example_app.sock;
    

    location /socket.io 
        #include proxy_params;
        proxy_http_version 1.1;
        #proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass http://unix:/path/to/app/example_app.sock;
    

注释掉的选项之前没有注释

错误

控制台:

polling-xhr.js:263 GET http://localhost:8000/socket.io/?EIO=3&transport=polling&t=MgTotN9 502 (Bad Gateway)
Access to XMLHttpRequest at 'http://localhost:8000/socket.io/?EIO=3&transport=polling&t=MgTotN9' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

nginx 错误日志(/var/log/nginx/error_log)

2019/05/09 14:16:35 [error] 11338#0: *1 upstream prematurely closed connection while reading response header from upstream, client: 127.0.0.1, server: , request: "GET /socket.io/?EIO=3&transport=polling&t=MgTpw36 HTTP/1.1", upstream: "http://unix:/path/to/app/example_app.sock:/socket.io/?EIO=3&transport=polling&t=MgTpw36", host: "localhost:8000", referrer: "http://localhost:3000/home"

请注意,在这两个示例中,http 请求(由应用程序服务的请求)都可以正常工作,只有套接字调用会出现问题。

【问题讨论】:

【参考方案1】:

flask_socketio 包装应用程序并根据可用的内容和调用方式使用不同的协议。它可以同时使用 HTTP 轮询和本机 Websocket,这两种不同的方法使用两种不同的协议。

如果eventletgevent独占使用,则使用轮询,即http请求。

如果使用 UWSGI,则使用原生 websockets (ws)。

如果 geventeventletuwsgi 一起使用,则使用来自 uwsgi 的本机 websocket 实现.

在我的例子中,我在客户端上使用了socket.io,它使用了 http 轮询,因此当我尝试使用 uwsgi 时,服务器期望一个本地 websocket 连接并且没有任何处理 http 轮询的东西。

所以为了解决我的问题,我测试了以下解决方案

删除 UWSGI,因为 flask_socketio + eventlet 或 gevent 会生成生产就绪配置 (docs) 使用原生 WS 和 UWSGI(有 eventlet 或 gevent 无关紧要,因为无论如何都使用 UWSGI 的实现)

【讨论】:

如果 gevent 或 eventlet 与 uWSGI 一起使用,我对使用哪个 websocket 实现有疑问。本地 websocket 实现是否不同,具体取决于它是 uWSGI 和上述之一还是只是 uWSGI。还是无论如何都使用uWSGI的实现? 你能发布你最终使用的配置吗?我有一个几乎相同的设置,我得到了同样的错误。不太清楚你在这个答案中的两个要点是什么意思。 抱歉,伙计们,我的回复太晚了,如果你们中的任何一个仍有问题,我可以发布我的意思的示例 我只是偶然发现了这个线程,寻找关于如何在我现有的系统(Nginx + UWSGI + Flask)中设置它的更好的文档。我很想看看你的配置 @steff_bdh,你能分享你的配置或你的经验吗?

以上是关于使用flask和flask-socketio配置nginx、uwsgi的主要内容,如果未能解决你的问题,请参考以下文章

[翻译] flask-SocketIO

使用 Flask-socketio 和 socketIO 客户端

Flask 和 Flask-SocketIO 集成和导入错误

Flask-SocketIO 未使用 Gevent/Gevent-websocket

flask-SocketIO 和 eventlet 有问题

Flask 和 Flask-SocketIO [重复]