Jump to content

Need help making SSE work


bernhard
 Share

Recommended Posts

Hi community,

last week I've tried to get PHP SSE working. Unfortunately without success. When using laragon it works, but on my DDEV setup and also on my live server it does not. I think there is some kind of compression and/or proxying going on, but I don't know how to debug or disable that.

@kongondo and @netcarver have mentioned SSE is "is so simple it is unbelievable at first." Maybe you can help me?

This is what I've tried:

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

$i = 0;
while(++$i<=10) {

  echo "event: ping\n";
  $curDate = date(DATE_ISO8601);
  echo 'data: {"time": "' . $curDate . '"}';
  echo "\n\n";

  file_put_contents(__DIR__."/dump.txt", "triggered@$curDate\n", FILE_APPEND);

  ob_end_flush();
  flush();

  if ( connection_aborted() ) break;

  sleep(1);
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
  const evtSource = new EventSource("sse.php", { withCredentials: true } );
  evtSource.onmessage = function(event) {
    console.log(event.data);
  }
  </script>
</body>
</html>

The file_put_contents works as expected, just the javascript does not get the messages from SSE:

nXS5Zs8.png

The eventstream is sent after 10 seconds at once and not on each event:

umlHRZc.png

Then the request is sent again and the same process starts from the beginning...

You can try the example here: http://sse.baumrock.com/sse.html

I'd be very happy if anybody could help me making this work 🙂 

  • Like 1
Link to comment
Share on other sites

You're misquoting me, @bernhard :)  Could you try this...

<script>
   var evtSource = new EventSource("sse.php")
   evtSource.addEventListener('ping', function(event) {
       var data = JSON.parse(event.data)
       console.log(data)
   }, false)
</script>

 

  • Like 1
Link to comment
Share on other sites

Thx @netcarver sorry I've quoted @kongondo and linked you in the same sentence 😉 

http://sse.baumrock.com/sse2.html

--> the event is received every 10s which is wrong as far as I understood how SSE should work. I'd expect a console.log() happen each second and have one open stream as long as I click on "close the connection"

Link to comment
Share on other sites

Hi @bernhard, this is what worked for me:

<?php

namespace ProcessWire;

$wire->addHook('/emit', function ($event) {
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    $reference = $event->pages->get('/')->page_selector->title;
    $selector = $event->pages->getRaw("title=$reference, field=id|title|text|image, entities=1");
    $result = json_encode($selector);
    while (true) {
        echo "event: ping" . PHP_EOL;
        echo "data: " . $result . PHP_EOL;
        echo PHP_EOL;
        ob_end_flush();
        flush();
        sleep(1);
    }
});

In the frontend then:

    // Init SSE and listen to php page emitter
    let source = new EventSource("/sse/emit/");
    source.addEventListener("ping", function(event) {
        getData(event)
    });

    // Retrieve ping data, parse it and pass the result to the resolveData function
    function getData(event) {
        return new Promise((resolve, reject) => {
            if (event.data) {
                let result = JSON.parse(event.data);
                resolve(result);
            }
        }).then((result) => {
            resolveData(result)
        });
    }

    // Resolve data and update alpine data
    function resolveData(result) {
        console.log(new Date().getSeconds()); // It logs correctly every 1 sec
        Object.assign(Alpine.store('sse'), {
            fragmentShow: true,
            id: result.id,
            title: result.title,
            text: result.text,
            image: result.image[0].data,
            imageDescription: result.image[0].description
        });
    }
Link to comment
Share on other sites

Hi @3fingers

thx but I think the problem is my server setup and I'm not good at debugging network stuff. See http://sse.baumrock.com/sse3.html where I have this code:

<?php
date_default_timezone_set("America/New_York");
header("Content-Type: text/event-stream");

$i = 0;
while(true) {
  $i++;

  // manual limit of 10 runs for development
  if($i>=10) return;

  echo "data: value of i = $i\n\n";

  while (ob_get_level() > 0) ob_end_flush();
  flush();

  if(connection_aborted()) break;

  sleep(1);
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>Open DevTools Console + Network Tab!</h1>
  <script>
  const evtSource = new EventSource("sse.php", { withCredentials: true } );
  evtSource.onmessage = function(event) {
    console.log(event.data);
  }
  </script>
</body>
</html>

It works locally: https://calip.io/HkCWbYAB#ueAgNM5v

But it does not work on my server: https://calip.io/DzhmJQo2#4mfu1aP6

 

I bet this has something to do with gzip but I can't make it work 😞 I thought maybe you guys know more about networking and http/tcp stuff and can help me debugging or making it work...

Link to comment
Share on other sites

23 minutes ago, Jan Romero said:

What does ob_get_status(true) output?

data: value of i = 1

array(1) {
  [0]=>
  array(7) {
    ["name"]=>
    string(22) "default output handler"
    ["type"]=>
    int(0)
    ["flags"]=>
    int(112)
    ["level"]=>
    int(0)
    ["chunk_size"]=>
    int(4096)
    ["buffer_size"]=>
    int(8192)
    ["buffer_used"]=>
    int(22)
  }
}
data: value of i = 2

array(0) {
}
data: value of i = 3

array(0) {
}
data: value of i = 4

array(0) {
}
data: value of i = 5

array(0) {
}
data: value of i = 6

array(0) {
}
data: value of i = 7

array(0) {
}
data: value of i = 8

array(0) {
}
data: value of i = 9

array(0) {
}
Link to comment
Share on other sites

A user in another forum found a solution!!!

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

$i = 0;
while(true) {
  $i++;

  echo "data: value of i = $i\n\n";
  echo str_pad('',8186)."\n";
  flush();

  if(connection_aborted()) break;
  sleep(1);
}

Reading the manual about flush() finally broucht me to the correct settings for my setup:

<?php
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
@apache_setenv('no-gzip', 1);
@ini_set('zlib.output_compression', 0);
@ini_set('implicit_flush', 1);
ob_implicit_flush(1);

$i = 0;
while(true) {
  $i++;
  echo "data: value of i = $i\n\n";
  flush();
  if(connection_aborted()) break;
  sleep(1);
}

I've tried several settings before regarding gzip, but nothing worked. For me the ob_implicit_flush(1) made the difference 🙂 

https://www.php.net/manual/de/function.flush.php

  • Like 2
Link to comment
Share on other sites

Ok I did some more testing and found out that using str_pad is the most reliable solution. At least in the environments I tested (laragon, DDEV, ubuntu+apache). So I can't say anything about ob_implicit_flush()

  • Like 1
Link to comment
Share on other sites

yeeha!! 😎

EODtQXT.gif

<?php
  /**
   * Center Buttons
   * @return Buttons
   */
  public function getButtonsCenter() {
    $buttons = parent::getButtonsCenter();
    $buttons->add([
      'name' => 'markClaimed',
      'icon' => 'check',
      'tooltip' => 'Mark selected items as claimed',
      'hidden' => '!selected',
      'appendMarkup' => '<div id=markClaimedConfirm hidden>...</div>',
      'callback' => [
        'name' => 'sendIDs',
        'confirmmarkup' => '#markClaimedConfirm',
        'loading' => 'Saving items...',
        'reload' => true,
        'execute' => function($input, self $grid) {
          $ids = $this->wire->sanitizer->intArray($input->ids);
          $note = $this->wire->sanitizer->text($input->note);
          foreach($this->wire->pages->findMany(["id" => $ids]) as $p) {
            $p->setAndSave(Effort::field_claimed, 1);
            $p->setAndSave(Effort::field_claimedwith, $note);
            $grid->sse("Saved page $p");
            sleep(1); // just for the screencast
          }
          $grid->sseDone();
          return ['success' => true];
        },
      ],
    ]);
    return $buttons;
  }

 

  • Like 4
Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...