Close search
Hoa

Hack book of Hoa\Eventsource

EventSource, or Server-Sent Events, is a technology allowing an HTTP server to send events to a client. The Hoa\Eventsource library allows to create an EventSource server.

Table of contents

  1. Introduction
  2. Events stream
    1. Data and events
    2. Reconnection
    3. Identifier
  3. Type and acceptation
  4. Conclusion

Introduction

The EventSource technology is a W3C standard. It allows a server to send events (also named notifications depending of the vocabulary we use) to a client. These events are constitued of data and, potentially, identifiers.

We can ask ourselves what are the differences between EventSource and WebSocket. These two solutions are in fact fundamentally different: EventSource is a technology based on the HTTP protocol and only provides a unidirectional communication. For a full-duplex and bidirectional usage, we will prefer the WebSocket protocol (see the Hoa\Websocket library). EventSource is based on the chunked HTTP mode allowing a server to send a response piece by piece (see the section 3.6.1, Chunked Transfer Coding of the RFC2616). Also, an EventSource server is more likely to be light, simple and it is designed to be robust regarding disconnections.

Events stream

The Hoa\Eventsource\Server allows to create an EventSource server. To start it, all we need is to instanciate the class. Thus, in Server.php:

$server = new Hoa\Eventsource\Server();

Now, let's write a very simple HTML client to execute our server, in index.html. We will only use the EventSource object and write listeners for the open and message events:

<pre id="output"></pre>
<script>
var output = document.getElementById('output');

try {
    var source    = new EventSource('Server.php');
    source.onopen = function () {
        output.appendChild(document.createElement('hr'));

        return;
    };
    source.onmessage = function (evt) {
        var samp       = document.createElement('samp');
        samp.innerHTML = evt.data + '\n';
        output.appendChild(samp);

        return;
    };
} catch (e) {
    console.log(e);
}
</script>

Next, let's see how to send events with associated data.

Data and events

To send data, we will use the Hoa\Eventsource\Server::send method, which takes the data to send as first argument. This data can contain different newline characters: \n, \r and even \r\n. Our server will write an infinity of messages every second:

while (true) {
    $server->send(time());
    sleep(1);
}

We can observe the result by opening the client in our favorite browser. Take care to start an HTTP server before.

All the data are coming to the client without particular distinction (nevertheless, we note that the order is preserved). For now, data are simple messages. What we would like is to classify these data based on associated event names. For example, to associate all the data to the tick event, we will write:

while (true) {
    $server->tick->send(time());
    sleep(1);
}

On our server instance, we call an attribute that has the name of our event, followed by our Hoa\Eventsource\Server::send method. If the event has a more sophisticated name, we can use the brackets syntax (be sure that your client supports this kind of events). For example, for the name ti-ck, we will write $server->{'ti-ck'}->send(time()).

If we set an event name for our data, consequently we have to modify the client by using addEventListener instead of onmessage:

        return;
    };
    source.addEventListener('tick', function (evt) {
        var samp       = document.createElement('samp');
        samp.innerHTML = evt.data + '\n';

Let's restart the server. The message is handled for a particular event. We are not limited, neither by the number of data, nor by the number of events.

Reconnection

When a connection is interrupted (because the client has lost the network for example, or when the server cuts the connection off), the client will try to reconnect after a certain time (the specification recommends around few seconds). We are able to set this delay to the client from the server by using the Hoa\Eventsource\Server::setReconnectionTime method with milliseconds. This method can be used at any time and whenever necessary. For example, we will indicate to the client to reconnect in case of a disconnection after exactly 10 seconds:

$server->setReconnectionTime(10000);

A non-positive time has no effect.

This method is particularly interesting as soon as we know when a next event will happen (for news stream, for games or other). Then, we are able to close the connection from the server, by having previously indicated to the client to reconnect after a certain time in order to receive a new event. While the server is disconnected, the HTTP server is released of one connection, that will free some resources.

Identifier

When we send data to the client, we are able to associate them to identifiers. The client will automatically remind the last received identifier and send it back to the server while reconnecting. This allows to check steps. To know the last identifier received from the client, we have the Hoa\Eventsource\Server::getLastId method, and to send a new identifier to the client, we have the second argument of the Hoa\Eventsource\Server::send method.

Let's take an example: our server will no longer make an infinite loop, but a randomly bounded one. Once the program reaches its end, the server will quit, and so, cut the connection off. The client will reconnect automatically after a certain time of its choice, or the time defined by the server, and then send the last received identifier. Our server will auto-increment the identifier and send it to the client (we have to send a message because the client does not give access to the identifiers):

$id = $server->getLastId() ?: 0;
$server->tick->send('last ID is ' . $id);
++$id;

for ($i = mt_rand(2, 5); $i >= 0; --$i) {
    $server->tick->send(time(), $id);
    sleep(1);
}

The identifier is not only a number: it is a string. If the identifier is null or empty, this will reinitialize the last identifier of the client to its original value.

Type and acceptation

The type of an EventSource server is given by the Hoa\Eventsource\Server::MIME_TYPE constant, namely the text/event-stream string. In order the server to be executed, the client must accept this type, it means that the HTTP Accept header must be present and must contain text/event-stream. If this is not the case, the server will send the 406 status code (see the section 10.4.7, 406 Not Acceptable of the RFC2616). In addition, the server will throw a Hoa\Eventsource\Exception exception from its constructor. It is possible to catch it in order to print our own error, such as:

try {
    $server = new Hoa\Eventsource\Server();
} catch (Hoa\Eventsource\Exception $e) {
    echo
        'You must send a request with ',
        '“Accept: ', Hoa\Eventsource\Server::MIME_TYPE, '”.', "\n";
    exit;
}

// …

We can test this behavior with cURL. In the first case, we only accept text/html:

$ curl -H 'Accept: text/html' http://127.0.0.1:8888/Server.php --verbose
* About to connect() to 127.0.0.1 port 8888 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
> GET /Server.php HTTP/1.1
> User-Agent: curl/a.b.c (…) libcurl/d.e.f
> Host: 127.0.0.1:8888
> Accept: text/html
>
< HTTP/1.1 406 Not Acceptable
< Date: …
< Server: …
< Content-Type: text/plain
< Content-Length: 62
<
You must send a request with “Accept: text/event-stream”.
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

In the next case, we accept text/event-stream:

$ curl -H 'Accept: text/event-stream' http://127.0.0.1:8888/Server.php --verbose
* About to connect() to 127.0.0.1 port 8888 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
> GET /Server.php HTTP/1.1
> User-Agent: curl/a.b.c (…) libcurl/d.e.f
> Host: 127.0.0.1:8888
> Accept: text/event-stream
>
< HTTP/1.1 200 OK
< Date: …
< Server: …
< Transfer-Encoding: identity, chunked
< Cache-Control: no-cache
< Content-Type: text/event-stream
<
data: last ID is 0

data: 1365685831
id: 1

data: 1365685832
id: 1

data: 1365685833
id: 1

* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

The Hoa\Eventsource\Server server also understands */* in the Accept header, which means all the types.

Conclusion

The Hoa\Eventsource library allows to create EventSource servers. These latters allow to send events to a client. The communication is unidirectional; for a bidirectional communication, we have to use Hoa\Websocket.

An error or a suggestion about the documentation? Contributions are welcome!

Comments

menu