Correct configuration for Flask SocketIO

9.7k views Asked by At

I have been following this tutorial to try and get Flask SocketIO running using nginx and gunicorn.

nginx

server {
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_redirect off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /socket.io {
        proxy_pass http://localhost:8000/socket.io;
        proxy_redirect off;
        proxy_buffering off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

    }

}

gunicorn_config.py

bind = '127.0.0.1:8000'
workers = 2
worker_class = 'socketio.sgunicorn.GeventSocketIOWorker'

In Supervisor I call my app with:

[program:gunicorn-couponmonk]
directory = ~/couponmonk_project
command =~/venv/py2.7/bin/python    ~/venv/py2.7/bin/gunicorn -c ~/venv/py2.7/lib/python2.7/site-packages/gunicorn/gunicorn_config.py __init__.py 
stdout_logfile = /var/log/gunicorn/couponmonk-std.log
stderr_logfile = /var/log/gunicorn/couponmonk-err.log
user = mint

With this configuration, my app (without using Flask SocketIO) works fine.

I'm just confused at to how to use socketIO. If I go to the address http://localhost:8000/socket.io, I get the response Internal Server Error.

The example HTML/Javascript (https://github.com/miguelgrinberg/Flask-SocketIO/blob/master/example/templates/index.html) contains this line:

var socket = io.connect('http://' + document.domain + ':' + location.port + namespace);

I have two questions:

1) What address is this variable supposed to point to?

2) Is my configuration (nginx, gunicorn) correct?

Sorry if this question is silly. I'm just confused as to how all this is supposed to work.

Thanks for your help.

**UPDATE**

This is the output of the nginx error.log file when I try and access http://localhost/socket.io.

2015/06/20 14:05:08 [debug] 1917#0: *1 http cleanup add: 00000000009F2170
2015/06/20 14:05:08 [debug] 1917#0: *1 get rr peer, try: 1
2015/06/20 14:05:08 [debug] 1917#0: *1 socket 12
2015/06/20 14:05:08 [debug] 1917#0: *1 epoll add connection: fd:12 ev:80000005
2015/06/20 14:05:08 [debug] 1917#0: *1 connect to 127.0.0.1:8000, fd:12 #2
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream connect: -2
2015/06/20 14:05:08 [debug] 1917#0: *1 posix_memalign: 0000000000A14170:128 @16
2015/06/20 14:05:08 [debug] 1917#0: *1 event timer add: 12: 60000:1434773168437
2015/06/20 14:05:08 [debug] 1917#0: *1 http finalize request: -4, "/socket.io?" a:1, c:2
2015/06/20 14:05:08 [debug] 1917#0: *1 http request count:2 blk:0
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A295E0
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream send request handler
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream send request
2015/06/20 14:05:08 [debug] 1917#0: *1 chain writer buf fl:1 s:439
2015/06/20 14:05:08 [debug] 1917#0: *1 chain writer in: 00000000009F21A8
2015/06/20 14:05:08 [debug] 1917#0: *1 writev: 439
2015/06/20 14:05:08 [debug] 1917#0: *1 chain writer out: 0000000000000000
2015/06/20 14:05:08 [debug] 1917#0: *1 event timer del: 12: 1434773168437
2015/06/20 14:05:08 [debug] 1917#0: *1 event timer add: 12: 60000:1434773168437
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A295E0
2015/06/20 14:05:08 [debug] 1917#0: *1 http run request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream check client, write event:1, "/socket.io"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream recv(): -1 (11: Resource temporarily unavailable)
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A15E38
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream dummy handler
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A15E38
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream process header
2015/06/20 14:05:08 [debug] 1917#0: *1 malloc: 00000000009E88A0:4096
2015/06/20 14:05:08 [debug] 1917#0: *1 recv: fd:12 244 of 4096
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy status 500 "500 Internal Server Error"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header: "Connection: close"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header: "Content-Type: text/html"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header: "Content-Length: 141"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header done
2015/06/20 14:05:08 [debug] 1917#0: *1 xslt filter header
2015/06/20 14:05:08 [debug] 1917#0: *1 HTTP/1.1 500 Internal Server Error
Server: nginx/1.4.6 (Ubuntu)
Date: Sat, 20 Jun 2015 04:05:08 GMT
Content-Type: text/html
Content-Length: 141
Connection: keep-alive

Not sure how helpful this is but I don't know where else to look for extra info.

I'm also curious as to why I can access this: http://localhost/socket.io123abc

and still get an Internal Server Error as opposed to a Not Found error?

I also updated my supervisord.conf file based on Miguel's answer below:

supervisord.conf

[program:gunicorn-couponmonk]
directory = /home/giri/couponmonk_project
command = /home/giri/venv/py2.7/bin/python /home/giri/venv/py2.7/bin/gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker __init__:app 
stdout_logfile = /var/log/gunicorn/couponmonk-std.log
stderr_logfile = /var/log/gunicorn/couponmonk-err.log
user = mint

/var/log/gunicorn/couponmonk-err.log

2015-06-20 14:30:11 [3821] [ERROR] Connection in use: ('127.0.0.1', 8000)
2015-06-20 14:30:11 [3821] [ERROR] Retrying in 1 second.
2015-06-20 14:30:12 [3821] [ERROR] Connection in use: ('127.0.0.1', 8000)
2015-06-20 14:30:12 [3821] [ERROR] Retrying in 1 second.
2015-06-20 14:30:13 [3821] [ERROR] Connection in use: ('127.0.0.1', 8000)
2015-06-20 14:30:13 [3821] [ERROR] Retrying in 1 second.

This list continues on for a while..

3

There are 3 answers

12
David K. Hess On BEST ANSWER

First, I'll mention that I don't recommend using SocketIO at all. It adds a little bit of useful functionality over WebSockets but it makes true load balancing with multiple workers (horizontal scaling) impossible unless you make clients sticky to individual workers or use something like Redis to share state information between them. I recommend taking a look at:

https://github.com/youen/gevent-websocket

Native WebSockets is simpler and easier and scales across multiple frontend workers for true load balancing. The code it takes to implement chat room logic is very minimal.

Having said that, here's the answer to your questions:

1) What address is this variable supposed to point to?

Your namespace is going to be "". (According to Miguel, author of Flask-SocketIO, the Javascript client automatically inserts the "socket.io" part.)

So, your io.connect url should be "http://[hostname]/".

2) Is my configuration (nginx, gunicorn) correct?

Ngnix config seems correct (though see next section). If you decide to use WebSockets, you might consider adding proxy_read_timeout 3600;. Otherwise, unless you have a chatty protocol, the WebSocket will be dropped by Nginx once a minute (the default value). (Also, according to Miguel, SocketIO has heartbeats which deal with this.)

Gunicorn config is not correct. With SocketIO you have a couple of choices:

  1. Set workers = 1 in gunicorn so that each SocketIO client is talking to the same worker process.
  2. Change your nginx configuration to use ip-hash command which will cause clients to be assigned to workers by client IP address.
  3. Use Redis or some other database to allow each worker to share state information (though it's not clear anybody has that working yet for Gevent SocketIO).

These choices are forced because SocketIO uses a stateful setup mechanism which breaks when you try to scale horizontally. See this issue for more information: https://github.com/abourget/gevent-socketio/issues/112

And here's a link to SocketIO's documentation talking about it also: http://socket.io/docs/using-multiple-nodes/

If you are getting an Internal Server Error then there is likely an exception being logged somewhere. Try to hunt it down and add it to your question.

Note that you can't test this by hitting that URL in the address bar of your browser – the browser doesn't speak the proper WebSocket protocol by default and will do nothing useful for you. WebSocket connections must be setup using the Javascript API.

Also, trying to hit that URL with the port number bypasses nginx - which is probably not what you want to do. Nginx usually listens at 80/443 and forwards requests to localhost:8000 (that's called a "reverse proxy" setup).

1
Miguel Grinberg On

I recommend that you make Flask-SocketIO work without nginx and gunicorn. Once you can get it to work through the native gevent server you can move to your real setup.

Regarding your questions:

1) What address is this variable supposed to point to?

Your connection statement is correct. Socket.IO will take the host, port and namespace and build the connection URL on its own, including the /socket.io component. You do not need to specify that in your connection.

2) Is my configuration (nginx, gunicorn) correct?

I think the nginx config is correct. You seem to have copied it direct from my documentation, and I have verified that it works.

The gunicorn config I'm not sure, you are not showing enough of your project to tell. The command that I use, which you should have in your supervisor config, is this:

gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker module:app

Where module is the main module of the application, and app is the name of the Flask application instance. You should definitely use a single worker, don't use two workers when using SocketIO.

0
Simon Lau On

For anyone who is reading this old post, gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker module:app command is outdated as Miguel mentioned in one of the github flask-socketio issue.