Welcome Guest, Not a member yet? Register   Sign In
How to implement EventSource on Codeignigter app?
#11

(02-24-2020, 02:09 PM)dave friend Wrote: Because the model is loaded by the controller it should be possible to send the message from the model. The key is sending a proper message. You cannot go to another controller until the processing is done though.
But I can send them during the process, yea? And what would it look like? This is what I have been trying to use in my model, but it dosen't work (Works in the controller, though, until I call the model):

Code:
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");

echo "Products have been found in that range!";
flush();
Reply
#12

I built a CodeIgniter version of the server-sent-events sample. It's modified from the Github example a tiny bit but works as expected.

Controller: Eventsource.php

PHP Code:
<?php

class Eventsource extends CI_Controller
{
    function __construct()
    {
        parent::__construct();
        $this->load->helper('url');
        $this->load->model('eventsource_m');
    }

    public function index()
    {
        $this->load->view('eventsource_v');
    }

    public function sse()
    {
        $this->eventsource_m->process();
    }


View: eventsource_v.php

PHP Code:
<html>
    <head>
        <meta charset="UTF-8">
        <title>Server-sent events demo</title>
    </head>
    <body>
        <button>Close the connection</button>

        <ul>
        </ul>

        <script>
            var button document.querySelector('button');
            var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> );
            console.log(evtSource.withCredentials);
            console.log(evtSource.readyState);
            console.log(evtSource.url);
            var eventList = document.querySelector('ul');

            evtSource.onopen = function () {
                console.log("Connection to server opened.");
            };

            evtSource.onmessage = function (e) {
                var newElement = document.createElement("li");

                newElement.textContent = "message: " + e.data;
                eventList.appendChild(newElement);
            };

            evtSource.onerror = function () {
                console.log("EventSource failed.");
            };

            button.onclick = function () {
                console.log('Connection closed');
                evtSource.close();
            };

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

Model: Eventsource_m.php

PHP Code:
<?php

class Eventsource_m extends CI_Model
{
    public function process()
    {
        date_default_timezone_set("America/New_York");
        header("Content-Type: text/event-stream");

        // 1 is always true, so repeat the while loop forever (aka event-loop)
        while (1)
        {
            $curDate date(DATE_ISO8601);

            // Send a simple message at 2 second intervals.
            echo 'data: This is a message at time ' $curDate"\n\n";

            // flush the output buffer and send echoed messages to the browser
            while (ob_get_level() > 0)
            {
                ob_end_flush();
            }
            flush();

            // break the loop if the client aborted the connection (closed the page)
            if (connection_aborted())
            {
                break;
            }

            // sleep for 2 seconds before running the loop again
            sleep(2);
        }
    }


Direct the browser to https://your_domain/eventsource and see a new message every two seconds. Clearly the EventSource messages can be sent from a model.

The only tricky bit was in the JavaScript with setting the URL for EventSource.

PHP Code:
var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> ); 

Notice how I had to explicitly concatenate single quotes around the output of base_url(). Without those JavaScript could not properly evaluate the argument. This is a common issue when combining PHP and JavaScript.

Using the browsers JavaScript console is important when trying to figure out what, if any, failures are occurring. I spotted the above issue right away.

Hopefully, all this helps.

It's not clear to me what you want to happen when processing is complete.

Based on your view it looks like you want to send data back to drive a progress-bar. If so, a message value equal to "100" would make for a good signal to disconnect the EventSource and move on to whatever is supposed to happen.
Reply
#13

(This post was last modified: 02-24-2020, 05:54 PM by SmokeyCharizard.)

(02-24-2020, 05:28 PM)dave friend Wrote: I built a CodeIgniter version of the server-sent-events sample. It's modified from the Github example a tiny bit but works as expected.

Controller: Eventsource.php

PHP Code:
<?php

class Eventsource extends CI_Controller
{
    function __construct()
    {
        parent::__construct();
        $this->load->helper('url');
        $this->load->model('eventsource_m');
    }

    public function index()
    {
        $this->load->view('eventsource_v');
    }

    public function sse()
    {
        $this->eventsource_m->process();
    }


View: eventsource_v.php

PHP Code:
<html>
    <head>
        <meta charset="UTF-8">
        <title>Server-sent events demo</title>
    </head>
    <body>
        <button>Close the connection</button>

        <ul>
        </ul>

        <script>
            var button document.querySelector('button');
            var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> );
            console.log(evtSource.withCredentials);
            console.log(evtSource.readyState);
            console.log(evtSource.url);
            var eventList = document.querySelector('ul');

            evtSource.onopen = function () {
                console.log("Connection to server opened.");
            };

            evtSource.onmessage = function (e) {
                var newElement = document.createElement("li");

                newElement.textContent = "message: " + e.data;
                eventList.appendChild(newElement);
            };

            evtSource.onerror = function () {
                console.log("EventSource failed.");
            };

            button.onclick = function () {
                console.log('Connection closed');
                evtSource.close();
            };

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

Model: Eventsource_m.php

PHP Code:
<?php

class Eventsource_m extends CI_Model
{
    public function process()
    {
        date_default_timezone_set("America/New_York");
        header("Content-Type: text/event-stream");

        // 1 is always true, so repeat the while loop forever (aka event-loop)
        while (1)
        {
            $curDate date(DATE_ISO8601);

            // Send a simple message at 2 second intervals.
            echo 'data: This is a message at time ' $curDate"\n\n";

            // flush the output buffer and send echoed messages to the browser
            while (ob_get_level() > 0)
            {
                ob_end_flush();
            }
            flush();

            // break the loop if the client aborted the connection (closed the page)
            if (connection_aborted())
            {
                break;
            }

            // sleep for 2 seconds before running the loop again
            sleep(2);
        }
    }


Direct the browser to https://your_domain/eventsource and see a new message every two seconds. Clearly the EventSource messages can be sent from a model.

The only tricky bit was in the JavaScript with setting the URL for EventSource.

PHP Code:
var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> ); 

Notice how I had to explicitly concatenate single quotes around the output of base_url(). Without those JavaScript could not properly evaluate the argument. This is a common issue when combining PHP and JavaScript.

Using the browsers JavaScript console is important when trying to figure out what, if any, failures are occurring. I spotted the above issue right away.

Hopefully, all this helps.

It's not clear to me what you want to happen when processing is complete.

Based on your view it looks like you want to send data back to drive a progress-bar. If so, a message value equal to "100" would make for a good signal to disconnect the EventSource and move on to whatever is supposed to happen.
Thank you so much! I'm going to try this soon! I suppose I should have mentioned that my cfa_model makes several requests to a SOAP api in the data conversion. While I'm working on implementing your ideas, would these calls effect what we are trying to do in any way? If so, how do I work around them?
Again, thank you a TON!

(02-24-2020, 05:28 PM)dave friend Wrote: I built a CodeIgniter version of the server-sent-events sample. It's modified from the Github example a tiny bit but works as expected.

Controller: Eventsource.php

PHP Code:
<?php

class Eventsource extends CI_Controller
{
    function __construct()
    {
        parent::__construct();
        $this->load->helper('url');
        $this->load->model('eventsource_m');
    }

    public function index()
    {
        $this->load->view('eventsource_v');
    }

    public function sse()
    {
        $this->eventsource_m->process();
    }


View: eventsource_v.php

PHP Code:
<html>
    <head>
        <meta charset="UTF-8">
        <title>Server-sent events demo</title>
    </head>
    <body>
        <button>Close the connection</button>

        <ul>
        </ul>

        <script>
            var button document.querySelector('button');
            var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> );
            console.log(evtSource.withCredentials);
            console.log(evtSource.readyState);
            console.log(evtSource.url);
            var eventList = document.querySelector('ul');

            evtSource.onopen = function () {
                console.log("Connection to server opened.");
            };

            evtSource.onmessage = function (e) {
                var newElement = document.createElement("li");

                newElement.textContent = "message: " + e.data;
                eventList.appendChild(newElement);
            };

            evtSource.onerror = function () {
                console.log("EventSource failed.");
            };

            button.onclick = function () {
                console.log('Connection closed');
                evtSource.close();
            };

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

Model: Eventsource_m.php

PHP Code:
<?php

class Eventsource_m extends CI_Model
{
    public function process()
    {
        date_default_timezone_set("America/New_York");
        header("Content-Type: text/event-stream");

        // 1 is always true, so repeat the while loop forever (aka event-loop)
        while (1)
        {
            $curDate date(DATE_ISO8601);

            // Send a simple message at 2 second intervals.
            echo 'data: This is a message at time ' $curDate"\n\n";

            // flush the output buffer and send echoed messages to the browser
            while (ob_get_level() > 0)
            {
                ob_end_flush();
            }
            flush();

            // break the loop if the client aborted the connection (closed the page)
            if (connection_aborted())
            {
                break;
            }

            // sleep for 2 seconds before running the loop again
            sleep(2);
        }
    }


Direct the browser to https://your_domain/eventsource and see a new message every two seconds. Clearly the EventSource messages can be sent from a model.

The only tricky bit was in the JavaScript with setting the URL for EventSource.

PHP Code:
var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> ); 

Notice how I had to explicitly concatenate single quotes around the output of base_url(). Without those JavaScript could not properly evaluate the argument. This is a common issue when combining PHP and JavaScript.

Using the browsers JavaScript console is important when trying to figure out what, if any, failures are occurring. I spotted the above issue right away.

Hopefully, all this helps.

It's not clear to me what you want to happen when processing is complete.

Based on your view it looks like you want to send data back to drive a progress-bar. If so, a message value equal to "100" would make for a good signal to disconnect the EventSource and move on to whatever is supposed to happen.
So I just tried your code out, and when I submit the form, a white page loads with the timestamp being loaded periodically.
     data: This is a message at time 2020-02-24T19:49:21-0500

     data: This is a message at time 2020-02-24T19:49:25-0500

     data: This is a message at time 2020-02-24T19:49:29-0500

     And so on.

This is good, but I want these tamestamps to be added to the original page, the one with the form, and for these timestamps to be updates the appeared beneath the submit button. It seems to be returning a new page instead...

Again, thanks for your help, Dave! Do you think you might know what is going wrong?
Reply
#14

(02-24-2020, 05:28 PM)dave friend Wrote: I built a CodeIgniter version of the server-sent-events sample. It's modified from the Github example a tiny bit but works as expected.

Controller: Eventsource.php

PHP Code:
<?php

class Eventsource extends CI_Controller
{
    function __construct()
    {
        parent::__construct();
        $this->load->helper('url');
        $this->load->model('eventsource_m');
    }

    public function index()
    {
        $this->load->view('eventsource_v');
    }

    public function sse()
    {
        $this->eventsource_m->process();
    }


View: eventsource_v.php

PHP Code:
<html>
    <head>
        <meta charset="UTF-8">
        <title>Server-sent events demo</title>
    </head>
    <body>
        <button>Close the connection</button>

        <ul>
        </ul>

        <script>
            var button document.querySelector('button');
            var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> );
            console.log(evtSource.withCredentials);
            console.log(evtSource.readyState);
            console.log(evtSource.url);
            var eventList = document.querySelector('ul');

            evtSource.onopen = function () {
                console.log("Connection to server opened.");
            };

            evtSource.onmessage = function (e) {
                var newElement = document.createElement("li");

                newElement.textContent = "message: " + e.data;
                eventList.appendChild(newElement);
            };

            evtSource.onerror = function () {
                console.log("EventSource failed.");
            };

            button.onclick = function () {
                console.log('Connection closed');
                evtSource.close();
            };

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

Model: Eventsource_m.php

PHP Code:
<?php

class Eventsource_m extends CI_Model
{
    public function process()
    {
        date_default_timezone_set("America/New_York");
        header("Content-Type: text/event-stream");

        // 1 is always true, so repeat the while loop forever (aka event-loop)
        while (1)
        {
            $curDate date(DATE_ISO8601);

            // Send a simple message at 2 second intervals.
            echo 'data: This is a message at time ' $curDate"\n\n";

            // flush the output buffer and send echoed messages to the browser
            while (ob_get_level() > 0)
            {
                ob_end_flush();
            }
            flush();

            // break the loop if the client aborted the connection (closed the page)
            if (connection_aborted())
            {
                break;
            }

            // sleep for 2 seconds before running the loop again
            sleep(2);
        }
    }


Direct the browser to https://your_domain/eventsource and see a new message every two seconds. Clearly the EventSource messages can be sent from a model.

The only tricky bit was in the JavaScript with setting the URL for EventSource.

PHP Code:
var evtSource = new EventSource(<?= "'".base_url('eventsource/sse''https')."'"?> ); 

Notice how I had to explicitly concatenate single quotes around the output of base_url(). Without those JavaScript could not properly evaluate the argument. This is a common issue when combining PHP and JavaScript.

Using the browsers JavaScript console is important when trying to figure out what, if any, failures are occurring. I spotted the above issue right away.

Hopefully, all this helps.

It's not clear to me what you want to happen when processing is complete.

Based on your view it looks like you want to send data back to drive a progress-bar. If so, a message value equal to "100" would make for a good signal to disconnect the EventSource and move on to whatever is supposed to happen.
I also found another error after adding your code. I get this error on the line where we have ob_end_flush();


<h4>A PHP Error was encountered</h4>

<p>Severity: Warning</p>
<p>Message:  Cannot modify header information - headers already sent by (output started at /opt/lampp/htdocs/codeigniter/application/models/cfa_model.php:139)</p>
<p>Filename: core/Common.php</p>
<p>Line Number: 570</p>
Reply
#15

(02-24-2020, 05:39 PM)SmokeyCharizard Wrote: So I just tried your code out, and when I submit the form, a white page loads with the timestamp being loaded periodically.
     data: This is a message at time 2020-02-24T19:49:21-0500

     data: This is a message at time 2020-02-24T19:49:25-0500

     data: This is a message at time 2020-02-24T19:49:29-0500

     And so on.

This is good, but I want these tamestamps to be added to the original page, the one with the form, and for these timestamps to be updates the appeared beneath the submit button. It seems to be returning a new page instead...

Again, thanks for your help, Dave! Do you think you might know what is going wrong?

Do you understand that when you submit a form the action URL (i.e. c_controller/classicFirearmByDate) is a redirect? That's why the page turns white. You have sent the browser to another controller.

The easiest fix requires more JavaScript. You need to catch the submit event and stop the normal HTML process by preventing the browser from doing the event. Then you use javascript to POST the data using the Fetch API. The argument to Fetch should be the action that you used for the form.

By doing all that you don't leave the page where you want to display the messages from EventSource.

Remove the $this->load->view(...) lines from C_controller::classicFirearmByDate()

As I said very early on in this thread, if you want to go to another page when processing is done, you will have to use javascript. This probably means redirecting from the Promise that Fetch returns. All that said, I have never combined Fetch and EventSource so I don't know what, if any, problems using them at the same time might cause. Happy Experimenting!
Reply
#16

(02-24-2020, 07:33 PM)dave friend Wrote:
(02-24-2020, 05:39 PM)SmokeyCharizard Wrote: So I just tried your code out, and when I submit the form, a white page loads with the timestamp being loaded periodically.
     data: This is a message at time 2020-02-24T19:49:21-0500

     data: This is a message at time 2020-02-24T19:49:25-0500

     data: This is a message at time 2020-02-24T19:49:29-0500

     And so on.

This is good, but I want these tamestamps to be added to the original page, the one with the form, and for these timestamps to be updates the appeared beneath the submit button. It seems to be returning a new page instead...

Again, thanks for your help, Dave! Do you think you might know what is going wrong?

Do you understand that when you submit a form the action URL (i.e. c_controller/classicFirearmByDate) is a redirect? That's why the page turns white. You have sent the browser to another controller.

The easiest fix requires more JavaScript. You need to catch the submit event and stop the normal HTML process by preventing the browser from doing the event. Then you use javascript to POST the data using the Fetch API. The argument to Fetch should be the action that you used for the form.

By doing all that you don't leave the page where you want to display the messages from EventSource.

Remove the $this->load->view(...) lines from C_controller::classicFirearmByDate()

As I said very early on in this thread, if you want to go to another page when processing is done, you will have to use javascript. This probably means redirecting from the Promise that Fetch returns.  All that said, I have never combined Fetch and EventSource so I don't know what, if any, problems using them at the same time might cause. Happy Experimenting!

Holy cow, thank you an absolute megaton, David! I'm working on implementing fetch now, but there seem to be a few caveats. How do I implement fetch in this instance? You've helped me a bunch, but can you help with this one last thing? Thanks!
Reply
#17

You should implement event.preventDefault() and Fetch('some_url') inside the submit event listener. That code can be added inside the <script> tags on the page that displays the form wehre your other JS is.

The documentation links I gave you should provide everything you need to sort this out. If not, there's always Google.

I don't think SOAP will interfere or complicate anything here. But it is hard to know without seeing the implementation.
Reply
#18

(02-24-2020, 09:06 PM)dave friend Wrote: You should implement  event.preventDefault() and Fetch('some_url') inside the submit event listener. That code can be added inside the <script> tags on the page that displays the form wehre your other JS is.

The documentation links I gave you should provide everything you need to sort this out. If not, there's always Google.

I don't think SOAP will interfere or complicate anything here. But it is hard to know without seeing the implementation.
If you want, I can link you to my github so you can sift through my code.
Reply
#19

Send me a PM telling me where your repo is and I'll take a peek.
Reply
#20

(This post was last modified: 02-25-2020, 09:28 AM by dave friend.)

You have private messaging disabled so I cannot send a PM in reply to the link you PM'd me.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB