[Guided Reading] HPBN - SSE (Server-Sent Events)

Purpose

Server-Sent Events enables efficient server-to-client streaming of text-based event data—e.g., real-time notifications or updates generated on the server.

It was an early addition to the HTML5 specification and is natively supported by most modern browsers.

Pros

  • Low latency (single, long-lived HTTP connection)
  • Efficient message parsing (no unbounded buffer)
    • Unlike a raw XHR connection, which buffers the full received response until the connection is dropped, an SSE connection can discard processed messages without accumulating all of them in memory.
  • Automatic tracking of last seen message and auto-reconnect
    • EventSource will automatically reconnect to the server and optionally advertise the ID of the last seen message, such that the stream can be resumed and lost messages can be retransmitted.
  • Client message notifications as DOM events

Cons

  • Each SSE consumes 1 TCP connection in HTTP/1.1 due to the long-lived HTTP connection

Examples

Facebook/Twitter updates, stock price updates, news feeds, sport results, etc.

Difference Between Push Technology

Elements

  • Browser

    • EventSource interface allows the client to receive push notifications from the server as DOM events
    • Implement XHR streaming and handle all the connection management and message parsing (allowing our applications to focus on the business logic)
  • Server

    • “Event Stream” data format is used to deliver the individual updates
    => Request
    // Client connection initiated via EventSource interface
    GET /stream HTTP/1.1 
    Host: example.com
    Accept: text/event-stream
    
    <= Response
    // Server response with "text/event-stream" content-type
    HTTP/1.1 200 OK 
    Connection: keep-alive
    Content-Type: text/event-stream
    Transfer-Encoding: chunked
    
    // Server sets client reconnect interval 15s if the connection drops
    retry: 15000 
    
    // Simple text event with no message type
    data: First message is a simple string. 
    
    // JSON payload with no message type
    data: {"message": "JSON payload"} 
    
    // Simple text event of type "foo"
    event: foo 
    data: Message of type "foo"
    
    // Multiline event with message ID and type
    id: 42 
    event: bar
    data: Multi-line message of
    data: type "bar" and id "42"
    
    // Simple text event with optional ID
    id: 43 
    data: Last message, id "43"
    

Demo

We use PHP because “PHP is the best language for web programming.

Demo source code from: HTML5 Server-Sent Events(伺服器發送事件) 教學範例 for PHP

Untitled

The first PCAP file is a normal version, the second one calls new EventSource() 3 times so there are 3 TCP connections in Wireshark conversations window.

Untitled

Untitled

server.php

<?php
ini_set('max_execution_time', 0);

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');    // Make Nginx flush buffer immediately

// Infinite loop
while (true) {
    // Data for transmission
    $data = array(
        'name' => 'Mr. Wang',
        'date' => date('Y-m-d H:i:s')
    );

    // Send with JSON encoded format
    echo "id: D121xxxxxx\ndata: " . json_encode($data);
    echo "\n\n";

    ob_flush();
    flush();

    // wait for 1 second
    sleep(1);
}

client.html

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HTML5 API Server-Send Event</title>

<script type="text/javascript">
window.onload = function() {
    document.querySelector('button').addEventListener('click', closeSSE, false);

    if (typeof(EventSource) !== 'undefined') {
        // server path
        var sse = new EventSource('server.php');

        sse.addEventListener('open', open, false);
        sse.addEventListener('message', message, false);
        sse.addEventListener('error', error, false);
    } else {
        alert("Browser doesn't support");
    }

    function closeSSE(event) {
        closeEventSource();
    }

    function open(event) {
        console.log('connected');
    }

    function message(event) {
        var pullData = JSON.parse(event.data);
        var newElement = document.createElement('li');
        newElement.innerHTML = pullData.name + ', ' + pullData.date;
        document.body.appendChild(newElement);
    }

    function error(event) {
        closeEventSource();

        alert('connection error');
    };

    function closeEventSource() {
        sse.close();

        alert('disconnected');
    }
}
</script>
</head>
<body>

<button>Disconnect</button>

</body>
</html>

References


comments powered by Disqus