配置nginx、uwsgi与flask和flask-socketio

9
我写了一个使用flask-socketio的Flask应用程序。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')
...

首次配置

Flask-SocketIO和uWSGI文档翻译为ini文件:

[uwsgi]
module = wsgi:app

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

http = :8000
gevent = 1000

这里不需要nginx配置,因为webpack会提供服务,而ini文件已经配置为直接响应'http=:port'的http请求。

控制台有时候会在routes.py中的connect事件中打印连接成功的信息"Connected! Awaiting commands...",但也可能会出现以下错误:

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

...

第二个配置

摘自这个问题的回答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请求(由应用程序提供)运行良好,只有套接字调用出现问题。
2个回答

3

flask_socketio 框架是在应用程序中进行封装,并根据可用的资源以及如何调用来使用不同的协议。它可以使用 HTTP 轮询和原生 Websockets 两种不同方法使用两种不同协议。

如果使用纯粹的 eventletgevent,则会使用轮询即 HTTP 请求。

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

如果同时使用了 geventeventlet 以及 uwsgi,则从 uwsgi 中使用原生 WebSockets 实现。

在我的情况下,我在客户端使用了 socket.io,它使用了 HTTP 轮询,因此当我尝试使用 uwsgi 时,服务器期望一个本机的 websocket 连接,但并没有任何处理 HTTP 轮询的东西。

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

  • 删除 UWSGI,因为 flask_socketio + eventlet 或 gevent 可以生成一个生产就绪的配置 (文档)
  • 使用原生 WS 和 UWSGI(无论是否有 eventlet 或 gevent,都会使用UWSGI的实现)

我有关于在使用gevent或eventlet与uWSGI一起使用时使用哪种websocket实现的问题。原生的websocket实现是否取决于是uWSGI和上述两者之一还是仅仅是uWSGI?还是无论如何都会使用uWSGI的实现? - wfehrnstrom
2
你能发一下你最终使用的配置吗?我的设置几乎完全相同,但我也遇到了相同的错误。不太确定你在这个答案中的两个要点是什么意思。 - That1Guy
2
抱歉,各位,我的回复非常晚了。如果你们中任何人仍然有问题,我可以发布我所说的示例。 - steff_bdh
2
我刚刚偶然发现了这个帖子,正在寻找更好的文档来设置我的现有系统(Nginx + UWSGI + Flask)。我很想看看你的配置。 - Cfreak
@steff_bdh,能否分享一下你的配置或经验文章? - iconique

0
请注意,根据socketio文档,在使用多个uwsgi实例(默认模式)时,必须配置缓存服务器,例如Redis,通过在创建SocketIO对象时指定message_queue选项。如果使用默认缓存,则客户端将连接,但在与服务器通信时会频繁出现400错误。确认这是您的问题的一种方法是将uwsgi配置为仅运行一个进程,例如processes=1。如果这消除了问题,则很可能是您的消息队列。有关如何配置它的信息可以在此链接找到:https://flask-socketio.readthedocs.io/en/latest/deployment.html#uwsgi-web-server

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接