I'm writing a webapp, based on Flask, gevent and Redis, which makes use of Server Sent Events.
I've gone through several questions on StackOverflow, and extensive search on google, but did not find any suitable answer that works for me, so here I am asking for the community help.
The problem is with the production stack, nginx+uwsgi: the browser receives updates regularly (and refreshes as expected) for about 30 seconds. After that the connection times out and the browser does not receive any update anymore, until the page is reloaded manually.
Since the whole thing works perfectly on localhost, with standard flask development server (connection alive after 30 minutes of idle), I'm pretty sure that the issue is on the uwsgi/nginx config. I've tried all the nginx/uwsgi settings I could think of but nothing, it keeps timing out after some seconds.
Does anybody have a clue ?
Here some code and configs.
nginx relevant production settings:
location / {
include uwsgi_params;
uwsgi_pass unix:/tmp/myapp.sock;
uwsgi_param UWSGI_PYHOME /srv/www/myapp/venv;
uwsgi_param UWSGI_CHDIR /srv/www/myapp;
uwsgi_param UWSGI_MODULE run;
uwsgi_param UWSGI_CALLABLE app;
uwsgi_buffering off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_cache off;
}
uwsgi production settings
[uwsgi]
base = /srv/www/myapp
app = run
home = %(base)/venv
pythonpath = %(base)
socket = /tmp/%n.sock
gevent = 100
module = %(app)
callable = app
logto = /srv/www/myapp-logs/uwsgi_%n.log
this is the javascript that the template executes to subscribe to the channel (for the time being, the template just refreshes the whole page when the server pushes some data)
<script type="text/javascript">
var eventOutputContainer = document.getElementById("event");
var evtSrc = new EventSource("/movers/monitor");
evtSrc.onmessage = function(e) {
console.log(e.data);
location.reload();
//eventOutputContainer.innerHTML = e.data;
};
</script>
This is the code I use to return the streamed data
from myapp import redislist
from flask import Response, Blueprint, stream_with_context
movers = Blueprint('movers', __name__, url_prefix='/movers')
r = redislist['r']
@movers.route("/monitor")
def stream_movers():
def gen():
pubsub = r.pubsub()
pubsub.subscribe('movers-real-time')
for event in pubsub.listen():
if event['type'] == 'message':
yield 'retry: 10000\n\ndata: %s\n\n' % event['data']
return Response(stream_with_context(gen()), direct_passthrough=True, mimetype="text/event-stream")
and finally the app is executed like this (DEBUG is True on localhost)
from myapp import app
from gevent.wsgi import WSGIServer
if __name__ == '__main__':
DEBUG = True if app.config['DEBUG'] else False
if DEBUG:
app.run(debug=DEBUG, threaded=True)
app.debug = True
server = WSGIServer(("", 5000), app)
server.serve_forever()
else:
server = WSGIServer("", app)
server.serve_forever()
after long hours on nginx log files and firefox js console, it turned out that the configurations shown in the question are perfectly fine.
The issue was the page reloading, this action kills and reinitializes the connection and therefore the retry command doesn't have any effect.
After removing that instruction the SSE updates work like a charm even after long time of inactivity.
Now the question is why this worked on the simpler development environment stack :-)
EDIT
indeed, after few more days, the connection still times out. I've made some time measures and found out that the time out interval is variable between some 30 seconds and several minutes of inactivity.
My conclusion is that the stack above is fine, while it's the amazon EC2 connection which expires after some variable inactivity time, since I'm still using a micro instance.
The final fix is the following JS snippet:
evtSrc.onerror = function(e) {
location.reload();
}
the page reloads When the connection is dropped (whatever the reason). The reloads are not expected to happen when the server sent events are frequent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With