Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure nginx, uwsgi with flask and flask-socketio

I've written a Flask app that uses flask-socketio. I am running the flask application on port 8000 and the client application separately on port 3000 (react-webpack). It works flawlessly in development mode (web server provided in flask). However, when trying to run using uwsgi I am having issues. These issues and configurations will be detailed below.

wsgi.py (remains constant)

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

__init__.py (remains constant)

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 (remains constant with obvious removal of actual logic)

...
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')
...

First configuration

taken from the documentation for flask-socketio and uwsgi translated to an ini file

[uwsgi]
module = wsgi:app

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

http = :8000
gevent = 1000

The nginx configuration is not needed here as webpack serves this and the ini file is configured to respond directly to http requests 'http=:port'

CONSOLE: This sometimes prints it being connected 'Connected! Awaiting commands...' from the connect event in routes.py however it will also give the following errors

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 process output:

...
[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

...

Second Configuration

Taken from this question. ini file

[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

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;
    }
}

Commented off options were previously left uncommented

Errors

Console:

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 error log (/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"

Note in both examples, http requests (ones served by app) work fine, only the socket calls give issue.

like image 416
steff_bdh Avatar asked May 09 '19 18:05

steff_bdh


People also ask

How do you deploy a Flask SocketIO?

The simplest deployment strategy is to start the web server by calling socketio. run(app) as shown in examples above. This will look through the packages that are installed for the best available web server start the application on it.

What is SocketIO in Flask?

Flask-SocketIO gives Flask applications access to low latency bi-directional communications between the clients and the server.


1 Answers

flask_socketio wraps the application and uses different protocols depending on what is available and how it is called. It can use both HTTP polling and native Websockets, two different methods using two different protocols.

If eventlet or gevent is used exclusively, then polling is used, that is, http requests.

If UWSGI is used, native websockets are used (ws).

If gevent or eventlet is used in conjunction with uwsgi, the native websocket implementation from uwsgi is used.

In my case, I used socket.io on the client, which uses http polling, therefore when I tried to use uwsgi, the server expected a native websocket connection and did not have anything that handled the http polling.

So in order to solve my issue I tested the following solutions

  • Remove UWSGI since flask_socketio + eventlet or gevent produces a production ready configuration (docs)
  • Use native WS and UWSGI (having eventlet or gevent does not matter since UWSGI's implementation is used regardless)
like image 136
steff_bdh Avatar answered Oct 11 '22 16:10

steff_bdh