Yii 1.1: Real-time display of server push data using Server-Sent Events (SSE)

20 followers

Server push technologies

There are several methods and techniques that come handy in the case you need to call an external resource periodically or if you are waiting for a server push, but I present here an easy and straightforward one using HTML5's Server-Sent Events (SSE).

Let's say you have an internal messaging system in your webapp, and you want to display messages dynamically and real-time to the relevant recipients.

Where's the demo?

No demo :-) But here are really easy steps to get it, and get it done.

Table structure

Let's keep things simple, we'll have a standard User model that is not represented here, and a Message model based on the following table structure:

CREATE TABLE `Message` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `toUserId` int unsigned NOT NULL,
  `message` text NOT NULL,
  `new` tinyint NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  KEY `toUserId` (`toUserId`)
);

View

Say you want messages to show in any part of the webapp, so you can add an empty div to your main layout and a simple jQuery snippet firing the SSE call, like this:

<?php
Yii::app()->clientScript->registerScript('message-update', '
if(typeof(EventSource) !== "undefined") {
    var source = new EventSource("' . CController::createUrl('site/getMessage') . '");
    source.onmessage = function(event) {
        $("#message").prepend(event.data).fadeIn(); // We want to display new messages above the stack
    };
}
else {
    $("#message").replaceWith("<div class=\"flash-notice\">Sorry, your browser doesn\'t support SSE.<br>Hint: get a real one :-)</div>"); // Don\'t be desperate, we\'ll see what we can do for you at the end of the wiki
}
', CClientScript::POS_READY);
?>
<div id="message"></div>

Controller (SiteController.php)

The app logic will be in our Site controller. The getMessage method will check at a defined interval (see below) whether there is any new message, and return a ready-to-display block that will be prepended to our message div.

So here's our controller method. Notice that you need to add the relevant access rule(s) for that method.

<?php
class SiteController extends Controller
{/* Add access rules if needed for getMessage */public function actionGetMessage()
    {
        $messageList = Message::model()->findAll(array(
                            'condition' => 'toUserId = :myId AND new = 1',
                            'order' => 'id DESC',
                            'params' => array(':myId' => Yii::app()->user->_id)
                        ));
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        echo "retry: 10000\n"; // Optional. We tell the browser to retry after 10 seconds
        if(count($messageList)) {
            foreach($messageList as $key => $message) {
                echo "data: <p>" . $message->message . "</p>\n";
            }
            //Now we run a bulk update query in order to flag the just retrieved messages (new = 0)
            $sql = 'UPDATE Message
                        SET new = 0
                        WHERE toUserId = ' . Yii::app()->user->_id;
            $command = Yii::app()->db->createCommand($sql);
            $command->execute();
        }
        flush();
    }}
?>

As you can notice, it's not real real-time, but a regular check at 10 second intervals. That is an optional parameter that falls back to the browser default if not specified.

Of course, the Message table update should be more accurate: with the above simplistic code, some messages could have been posted to the user during the method execution, so they would be flagged —wrongly— whatsoever.

Browser support aka does it work in IE?

Internet Explorer and Android browser (all versions) don't support Server-Sent Events out of the box. Neither do older versions of Firefox (< 6), Chrome (< 6), Safari (< 5), iOS Safari (< 4), or Opera (< 11).

So should we drop it until IE and Android support it?

It's up to you. There's a polyfill that gives SSE/EventSource support to IE 8+ and Firefox 3.5+.

More information

Read more about SSE and Websockets and other techniques:

Version française

Tutoriel Yii : Affichage en temps réel de données en provenance du serveur à l’aide des Server-Side Events (SSE)

Total 16 comments

#17186 report it
lxvi at 2014/05/11 01:43am
Aren't you really cheating?

Let's be honest: this is not a true SSE implementation because it is not the server that decides when to push the message, it's the retry of the EventSource object which is experiencing an error at the end of the stream and therefore re-sending its http request.

A true implementation would have a daemon process running on the server that sent events ONLY initiated on its own internal event context and not as the result of network traffic from the client every ten seconds. You might as well just use setInterval and an AJAX POST.

#13507 report it
Firebreaker at 2013/06/02 10:43pm
Got it. Remember \n\n

OK, here is the thing. What you were posting was good. But not fully correctly. I read HTML5 SSE articles and found out that to complete the SSE from server-side for 1 call it must end with with double '\n' e.i '\n\n'. Please update your post, before flush(); put echo '\n\n'; to end the request. Thanks for tutorial though. It helped me a lot to figure this out.

#13506 report it
bennouna at 2013/06/02 01:22pm
RE: Not working

@Firebreaker

What browser / version are you using?

Are you seing the calls being made in Firebug at your time interval? Do they succeed? (Response code 200)

PS You won't see the server-returned data in Firebug, you should see only the calls and their reponse code and headers.

#13504 report it
bennouna at 2013/06/02 01:18pm
RE: can i control it

@diaa

It's up to you, but the wiki shows how to send specific messages to specific users

#13503 report it
Firebreaker at 2013/06/02 11:49am
Not working

Browser gets data by ajax but response is null or no response, echo "data: 'data'"; in actionGetMessage not writing anything to browser.

#13321 report it
diaa at 2013/05/22 10:07am
can i control it

can I use it to send mesage to a specific user(s) or it only can broadcast the messages ???

#9646 report it
Fahd at 2012/09/01 02:52am
RE: RE: Problem implementing this!

Chrome version is 21, I am not using access control and there is that div to be populated by incoming messages.

Also, I set a break point in the "onmessage" callback but the JS doesn't break at it, meaning that "onmessage" event is never raised! :O

#9645 report it
bennouna at 2012/08/31 04:02pm
RE: Problem implementing this!

@sfsultan: Well your code looks ok to me… Which Chrome version are you using?

Also, are you sure your getMessage action is allowed in your controller's accessRules?

On a side note, have you included the #message div in your view?

<div id="message"></div>

I'm not sure it would break anything though if absent.

#9644 report it
Fahd at 2012/08/31 02:48pm
Problem implementing this!

Spent alot of time trying to figure this out but haven't been successful.

JS code:

Yii::app()->clientScript->registerScript('message-update', '
if(typeof(EventSource) !== "undefined") {
    var source = new EventSource("' . CController::createUrl('site/getMessage') . '");
    source.onmessage = function(event) {
        console.log(event.data);
        $("#message").prepend(event.data).fadeIn(); // We want to display new messages above the stack
    };
}
else {
    $("#message").replaceWith("<div class=\"flash-notice\">Sorry, your browser doesn\'t support SSE.<br>Hint: get a real one :-)</div>"); // Don\'t be desperate, we\'ll see what we can do for you at the end of the wiki
}
', CClientScript::POS_READY);

And the function

public function actionGetMessage()
{
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    echo "retry: 10000\n";
    echo "data: A new message\n";
    flush();
}

The problem is that I'm not getting any response back whatsoever. This should print to the chrome console but its empty.

Though request header seems fine.

#9618 report it
bennouna at 2012/08/29 07:42am
Re: Not worked

@sirin_ibin What exactly have you tried and has not worked?

#9613 report it
sirin k at 2012/08/28 10:45pm
Not worked

i tried this but not worked.

#8034 report it
nitropenta at 2012/05/04 03:06pm
Websockets & PHP

At one of my projects I realize a multipe choice quiz with node.js and jqTouch - that works very good. I operate about 100 iPads for the quiz and PowerPoint at "operating panel" to start and stop the Q&A plus show a bar-chart for the results.

The disadvantage of this is a pure work at javascript ;-) and not PHP

I hope the follow links can you help for PHP:

  • http://code.google.com/p/phpwebsocket/
  • http://bohuco.net/blog/2010/12/php-websocket-class-new-version/
  • http://mylittlehacks.appspot.com/phpwebsocket
  • https://github.com/nicokaiser/php-websocket
  • https://github.com/lemmingzshadow/php-websocket
  • http://www.flynsarmy.com/2012/02/php-websocket-chat-application-2-0/
  • http://code.google.com/p/phpws/
  • http://www.benny-design.eu/webdesign/php/websocket-mit-php/
  • http://blogs.msdn.com/b/fiddler/archive/2011/11/22/fiddler-and-websockets.aspx
#8025 report it
bennouna at 2012/05/04 06:24am
RE: Websockets

@nitropenta It's the first push technology I had —recently— the opportunity to implement. I found it really easy and straightforward, and moreover, it serves the purpose, doesn't it? After a post in the forum, I imagined there could be a little wiki for it :)

For Websockets, I've also read about them, and thanks for the Google Code link. It looks interesting. I should try them when I have the chance, especially for bilateral stuff.

#8023 report it
nitropenta at 2012/05/04 03:14am
missed lines

... no, I read the "More Info" and I know the first article (Dimitry Sheiko)

but what is your reason to work with SSE? I dont see the benefit from long-polling to SSE...

#8022 report it
bennouna at 2012/05/04 02:39am
RE: Websockets

Thanks for your feedback. Maybe you missed the last two lines of the wiki ("More information"):

#8021 report it
nitropenta at 2012/05/04 02:14am
Websockets

Why do you work not with websockets?

Sure, a good implementation is with node.js as server - but in JavaScript and not in PHP - so its possible to implement the server funktionality in PHP

see http://it-republik.de/php/artikel/WebSocket-Implementierung-mit-PHP-3816.html (german) or http://code.google.com/p/phpwebsocket/

Leave a comment

Please to leave your comment.

Write new article