Welcome Guest, Not a member yet? Register   Sign In
How to capture EventSource messages while process is running?
#6

(This post was last modified: 02-27-2020, 03:44 PM by zahhar.)

Well, after I was ashamed with my quick and unprofessional response into this thread I could not get asleep without  putting my hands on this.

Few interesting learnings, probably would be useful for someone:

  1. Code from above did not work for me. I observed weird behaviour in all browsers: after receiving exactly 3 messages from the server, my browser disconnected and attempted to reconnect. It was an endless loop.
  2. It turned out that my web-server was running with ob_get_level() == 2. I could get rid of the issue only after I recursively flushed all buffers in the CI controller.
  3. Finally, I had a problem with saying both server and client to stop working. Client was complaining on suddenly appearing wrong MIME not equal to expected 'text/event-stream', while client always wanted to restart listening to the stream, and could not understand that server is done with messaging. 
So here is my code that works well both on server and client side:

Client (trivial wrapping omitted):

Code:
<button onclick="sse()">Start SSE</button>

<script>
    function sse() {
      let eventSource;
      eventSource = new EventSource('<?= route_to("stream")?>');

      eventSource.onopen = function(e) {
        console.log("eventSource: connection opened");
      };

      eventSource.onerror = function(e) {
        console.log("eventSource: error occurred");
        if (this.readyState == EventSource.CONNECTING) {
          console.log(`eventSource: reconnecting (readyState=${this.readyState})...`);
        } else {
          console.log("eventSource: connection failed");
        }
      };

      eventSource.onmessage = function(e) {
        console.log("eventSource: " + e.data);
        if (e.data == 'done') {
          this.close();
          console.log("eventSource: server is done");
        }
      };
    }
</script>

And method in CI Controller that is called from above via named route for simplicity:

PHP Code:
<?php namespace App\Controllers;
use 
CodeIgniter\I18n\Time;

class 
Streamer extends BaseController {

    public function stream() {
        header("Content-Type: text/event-stream");
        header("Cache-Control: no-cache");
        
        
// My server had 2 output buffers already opened before it reached here
        // one was "default output handler" from php.ini
        // another was "Closure::__invoke" I have no clue where from
        // here might be even more levels if gzip is enforced for example
        while (ob_get_level() > 0) {
            ob_end_flush();
        }

        // Here will be data sourcing from DB and processing by external API
        for ($i=0$i 10 $i++) {
            echo "data:""Server time is ".date(DATE_ISO8601)."\n""id: $i\n\n";
            flush();
            sleep(1);
        }
        echo "data: done\n\n"// Gives browser a signal to close connection
        flush();
        exit; // without it was throwing "Headers already sent" warning
    }


This works like a charm in Safari, Chrome and Firefox (all desktops). No need for websockets.

Useful articles I found on this topic:  Thanks once again for starting this thread!

Probably, CI4.x could include some out of the box support for streaming, what do you think?
Reply


Messages In This Thread
RE: How to capture EventSource messages while process is running? - by zahhar - 02-27-2020, 03:37 PM



Theme © iAndrew 2016 - Forum software by © MyBB