5

I am working on a simple web app which is done with angular and cherrypy (prototyping at the moment). I am uploading two files, and then calling a external python program within cherrypy to process them, using subprocess (popen). I am able to do this so far. What I want to achieve is the output from the external program (which I catch via popen) passed on to the client. My issue is that, I am trying to setup server sent events on cherrypy and having no success.

Here is my cherrypy method I am exposing (an example one from web):

@cherrypy.expose
def getUpdate(self):
    #Set the expected headers...
    cherrypy.response.headers["Content-Type"] = "text/event-stream;charset=utf-8"
    def content():
         yield "Hello,"
         yield "world"
    return content()

And here is the javascript client code (I have CORS enabled and working):

var sseEvent = new EventSource('http://localhost:8090/getUpdate');
sseEvent.onmessage = function (event) {
    console.log(event);
};
sseEvent.onopen = function (event) {
  //console.log("I have started...");
};

I have looked at this question and in this blog. Yet, the onmessage event on the EventSource object is not firing, on calling the function from the server side. My understanding is that, you can call the function from the server side and it will catch the event from the browser. I am wrong or the setup is wrong?

Community
  • 1
  • 1
Vas Vasanth
  • 139
  • 2
  • 9

2 Answers2

4

So I figured out that with SSEs, I need to sent data in a particular format. i.e.

  • data: "foo \n\n"

or for json

  • data: "{\n data: "msg" : "foo", \n data: "id" : "boo", \n data: "}\n\n

What I wanted was a retry format, to keep polling the server after n seconds. So the cherrypy function now is:

@cherrypy.expose
def getUpdate(self, _=None):
    cherrypy.response.headers["Content-Type"] = "text/event-stream;charset=utf-8"
    if _:
        data = 'retry: 200\ndata: ' + str( self.prog_output) + '\n\n'
        return data
    else:
        def content():
            data = 'retry: 200\ndata: ' + str( self.prog_output) + '\n\n'
            return data
        return content()

getUpdate._cp_config = {'response.stream': True, 'tools.encode.encoding':'utf-8'}

, where the message sent now is with a

'retry: n microseconds'

This will sent the data every n microseconds. Now the EventSource onmessage event is being triggered, and I am happily reading the output from the program sent from the the server. :)

A good read for SSE (as mentioned in many posts): here

Vas Vasanth
  • 139
  • 2
  • 9
2

To complement this self answered question, I made this fully functional example which consist in two files.

This documentation was very helpful.

Happy fiddling to all !

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>title</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div>server-sent events</div>
    <h3>sse_handler.onmessage()</h3>
    <ul id="sse_message"></ul>

<script>

    // create a sse handler
    var sse_handler = new EventSource('http://localhost:8080/getUpdate');

    sse_handler.onmessage = function (event) {
        console.log("-- sse_handler.onmessage()", event);
        /* the onmessage method catch all generic message, those with no event field
        */
        var h_ul = document.getElementById('sse_message');
        var h_li = document.createElement("li");

        h_li.innerHTML = event.data;
        h_ul.appendChild(h_li);
    };

    sse_handler.onopen = function (event) {
        console.log("-- sse_handler.onopen()", event);
    };

</script>
</body>
</html>

and server.py

#!/usr/bin/env python3

import datetime

import cherrypy

from pathlib import Path

class TestServerSentEvent(object):
    @cherrypy.expose
    def index(self):
        return Path("index.html").read_text()

    @cherrypy.expose
    def getUpdate(self, * pos, ** nam):
        cherrypy.response.headers["Content-Type"] = "text/event-stream;charset=utf-8"
        return 'retry: 1200\ndata: {0}\n\n'.format(self.output())

    def output(self) :
        d = datetime.datetime.now()
        return f"TEST - {d}"

    getUpdate._cp_config = {'response.stream': True, 'tools.encode.encoding':'utf-8'}

if __name__ == '__main__':
    cherrypy.quickstart(TestServerSentEvent())
yota
  • 2,020
  • 22
  • 37