Server-sent events


1Presentation

This page documents Temma's server-sent events functionality, which enables one-way communication (from server to browser) in real time.

Please note: if you're looking for documentation on asynchronous event management, check out the ZeroMQ, SQS and Beanstalk data sources.

When a Javascript code connects to a URL to receive SSEs (server-sent events), the connection remains open, rather like a websocket. If the connection is cut, the client will automatically reconnect.

Temma has introduced a new type of controller, the event controller, derived from the basic \Temma\Web\EventController object. These controllers can only be used to process SSEs, and have a few differences from conventional controllers (which derive from \Temma\Web\Controller):

  • They are designed to run for as long as required.
  • They don't execute a view at the end of execution, as they send event messages during execution.
  • No view = no template = no template variables.
  • When a value is defined (in the way template variables are usually defined), it is immediately sent to the client, which receives it as an event.

Please note that a large number of connections may remain open (as many as there are clients connected to the site). Check that your server accepts a sufficient number of simultaneous connections.


2Example

Let's imagine a very simple web page. It includes a Javascript code that will connect to the URL /message/fetch, and display the events of the "demo" channel in a list.

Voici le code HTML de la page:

<html>
<head>
    <!-- loading Javascript code -->
    <script src="event-manager.js"></script>
</head>
<body>
    <!-- list containing event messages -->
    <ul id="liste">
    </ul>
</body>
</html>

And the Javascript code (file event-manager.js):

// connection to the event stream
const evtSource = new EventSource("/message/fetch");

// process incoming events on the "demo" channel
evtSource.addEventListener("demo", function(event) {
    // retrieve text (serialized as JSON in the event)
    const str = JSON.parse(event.data);

    // create new list element
    const newElement = document.createElement("li");
    // add event text as list element content
    newElement.textContent = str;

    // add the element to the list (in the HTML page)
    document.getElementById("liste").appendChild(newElement);
});

Here is the code of the controller that will send the events (it sends a text message every 2 seconds):

// Message controller
class Message extends \Temma\Web\EventController {
    // fetch action
    public function fetch() {
        $i = 1;
        // infinite loop
        while (true) {
            // send an event on the "demo" channel
            $this['demo'] = "Message n°$i";
            // increment counter
            $i++;
            // wait 2 seconds before sending next message
            sleep(2);
        }
    }
}

Every two seconds, the controller will send an event, and the client will add the event message to the list in the web page.


3Events controller principles

Apart from the specific features listed above (no template or view), events controllers work in much the same way as conventional controllers. In fact, the \Temma\Web\EventController object inherits from \Temma\Web\Controller.

Thus, the concepts of root action, proxy action and default action remain identical, as do controller initialization and finalization (see controller documentation).

Plugins also work in the same way. Bear in mind, however, that post-plugins will not be processed each time an event is sent, but only at the end of execution (knowing that execution may be interrupted by the server due to an excessively long execution time).


4Sending events

As seen in the example above, to send an event, all you need to do is define a value in the same way as you normally define a template variable, using associative array writing. The key is the name of the event channel, and the value can contain any PHP data (which will be serialized to JSON).

Examples:

// send two text messages on the "msg" channel
$this['msg'] = "First message";
$this['msg'] = "Second message";

// send a list of items on the "update" channel
$this['update'] = [
    123 => [
        'id'     => 123,
        'name'   => "Neptune project",
        'author' => "Jane Doe",
    ],
    456 => [
        'id'     => 456,
        'name'   => "Pluto project",
        'author' => "John Doe",
    ],
];

Each time data is sent, the controller first automatically checks whether the connection with the client is still open. If this is not the case, a \Temma\Exceptions\FlowQuit exception is raised; if it is not intercepted, this will lead to a processing halt (see flow documentation).

If the message could be sent, the maximum script execution time is extended. The value used is the one configured for the max_execution_time directive in the php.ini file. If this value is not set, the maximum execution time is set to 30 seconds. If this value is set to zero (unlimited time), it is not modified.


5Event channel management

It is possible to find out whether messages have been sent on a channel, and if so how many.

Examples:

// has a channel already been used?
if (isset($this['myChannel']))
    print("The channel 'myChannel' has been used.");

// retrieve the number of events sent on a channel
$nbr = $this['myChannel'];

// reset a channel (act as if it had never been used)
unset($this['myChannel']);

Event controllers offer specific methods that can be used in action code.

$this->_checkConnection()
Checks that the connection to the client is still open. If not, an exception \Temma\Exceptions\FlowQuit is thrown (see flow documentation).

$this->_renewTimeLimit()
This method first calls the _checkConnection() method, then extends the maximum script execution time. The value used is the one configured for the max_execution_time directive in the php.ini file. If this value is not set, the maximum execution time is set to 30 seconds. If this value is set to zero (unlimited time), it is not modified.

$this->_ping()
Sends an event on the ping channel, which contains an associative array whose time key contains the current date and time in ISO 8601 format.