Search code examples
javascriptphpserver-sent-events

Why my php SSE file on server continues working when browser is closed if nothing is sent?


I have a curious behavior with SSE.

I have 2 files, one for server side and one for browser client.

serverside.php is this:

<?php
  header('Content-Type: text/event-stream');
  header('Cache-Control: no-cache');
  ob_end_clean();

  while( true ) {
    set_time_limit(30);

    $time = date('d/m/Y H:i:s');

     echo 'data: ' . $time . "\n\n";
     flush();

    error_log('i am here!');
    sleep(1);
   }
?>

and client side file is this:

<!DOCTYPE html>
<html>
<body>

<div id="result"></div>
<script>
    var source;

    if(typeof(EventSource) === "undefined") {
      alert('Problem with your browser.');
    }

    source = new EventSource("serverside.php");
    source.onopen = function (event) {
      console.log('streaming SSE opened');
    };

    source.onmessage = function(event) {
      console.log('Streaming Message Received');
      document.getElementById("result").innerHTML = event.data;
    };

    source.onerror = function (event) {
    console.log('ERROR EventSource');
  }
</script>

</body>
</html>

If i test this all is running ok. I got the refreshed time in browser and when i close the tab in my browser, serverside.php doesn't send more 'i am here!' message to log. That is the right behavior. When browser tab is closed, serverside.php must stop.

But now, if i remove or comment these lines in serverside.php file:

// echo 'data: ' . $time . "\n\n";
// flush();

the serverside.php file remains like this:

<?php
  header('Content-Type: text/event-stream');
  header('Cache-Control: no-cache');
  ob_end_clean();

  while( true ) {
    set_time_limit(30);

    $time = date('d/m/Y H:i:s');

     //  echo 'data: ' . $time . "\n\n";
     //  flush();

    error_log('i am here!');
    sleep(1);
   }
?>

That is, no flush data at all.

If now I load index.php i will not receive nothing from serverside.php. But in this case, when i close the browser tab, serverside.php continues working and sending to log 'i am here' messages.

Also, if serverside.php sends data and after some messages sent stops to send more data, the behavior is the same. 'i am here' messages continues to arrive after tab close.

  • If I close browser tab while data is sending from serverside.php, all works ok.
  • If I close browser tab after data was sent and nothing is sending from serverside.php, serverside.php continues working.

What is the explication for this?

I'm completely stuck.


Solution

  • (I'll assume Apache is the server, and you are using mod_php, but the basic idea should be the same whatever web server.)

    If your PHP script never does a flush() the Apache buffer contents will only get sent to the client (your browser) when the buffer gets full. And that never happens because your script never does an echo.

    And the way Apache realizes the client has disconnected is by trying to send it something on the socket and getting an error. And this never happens because you never send anything! (Yep, to realize a TCP/IP socket is not working any more you have to try using it and fail; there is no simple status you can look at.)

    The best solution I know is to have the server side script send a keep-alive message. I.e. a blank message, or a timestamp, every (e.g.) 40 seconds. If your genuine data stream has gone quiet, but you are sending a keep-alive ever 40 seconds, then your average time to notice a client has disconnected will be 20 seconds.

    (And this is definitely a good idea, especially if clients connect and disconnect frequently, as otherwise they are effectively holding both a socket and an Apache process open.)