Noah
Noah

Reputation: 41

JavaScript EventSource triggers onerror after every received message

I have a web app utilizing Python Flask where I am trying to use Server Sent Events (SSEs) to push messages to web pages without having to poll from the client side or request that data. I'm using Redis to listen for new data which will then be sent to the web page. To start and make sure that I can use SSEs correctly, I've used a template similar to an example like this (How to implement server push in Flask framework?).

The problem I'm running into is that every time the client receives a message, the EventSource onmessage() method is called and delivers the message properly, but then the .onerror() method immediately gets triggered, causing the client to try to reconnect. This results in the '/listen' endpoint being called over and over and over, leading to the creation of many redis pubsub objects that are redundant and subscribe to the same channels.

The python code that runs the flask app is as follows

import flask
from flask_bootstrap import Bootstrap
from redis import Redis
from flask_wtf import FlaskForm

app = flask.Flask(__name__)
bootstrap = Bootstrap(app)
red = Redis(host='localhost', port=6379, db=0)

@app.route('/listen')
def listen():
    pubsub = red.pubsub()
    pubsub.subscribe('chat')
    def stream():
        for message in pubsub.listen():
            if message['type'] == 'message':
                msg = f"data: {message['data'].decode('utf-8')}\n\n"
                yield msg
    for msg in stream():
        return Response(msg, mimetype='text/event-stream')

@app.route('/sse_page', methods=['GET', 'POST'])
def sse_page():
    form = FlaskForm()
    return render_template('sse_page.html', title='Server Push Testing', form=form)

if __name__ == "__main__":
    app.run(port=8000, threaded=True, debug=True)

The corresponding section of sse_page.html where I try to open the EventSource and listen for the events stream is

    <body>
        <div id="target_div">Watch this space...</div>
    </body>

    <script>
         var source = new EventSource("/listen");
         source.onmessage = function (event) {
             console.log('data: ', event)
             $("#target_div").text(event.data)
         };
         source.onerror = function (event) {
             console.log('error ', event)
         };
         source.onopen = function (event) {
             console.log('open', event)
         };
    </script>

Using the redis-cli to send messages like those seen here (and transcribed below)

127.0.0.1:6379> publish chat a
(integer) 1
127.0.0.1:6379> publish chat b
(integer) 2

Result in the console logging messages from Eventsource.onopen(), Eventsource.onmessage(), and Eventsource.onerror() for every single message, as seen here.

I cannot figure out why the eventsource has an error after every single message that is received or how to prevent that from happening.

Upvotes: 2

Views: 1544

Answers (1)

Noah
Noah

Reputation: 41

The answer to this question is NOT a problem in the code itself.

What this ended up being was an issue with the anti-virus security that was being used on the machine. Using Sophos AV Endpoint was causing each SSE to be treated as a download, and so any of the text data was unable to be streamed until the 'download was complete'.

This is (apparently) a known issue (see link https://community.sophos.com/on-premise-endpoint/f/sophos-endpoint-software/74878/server-sent-events-blocked-by-download-scanner) and there are a couple of ways to deal with it. You can either disable web scanning by Sophos (which does not work if you do not have administrator permissions) or run the flask app securely over HTTPS (https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https has a great tutorial).

Credit too should go to this post (JavaScript EventSource SSE not firing in browser), which is how I was able to find that the AV software was what ended up causing my issues.

Upvotes: 1

Related Questions