In this part, the server application uses the Indy HTTP server uses SSE to continuously send events to the JavaScript EventSource.


Part 3: the demo application, now streaming

Ingredient #1: the HTML page with JavaScript

The script has not changed, it reads two data items from the ping event:

  • a time stamp in ISO 8601 format
  • the peer data, which is the IP address and port number
<!DOCTYPE html>
<html>
	<head>
		<title>SSE example</title>
	</head>
	<body>
		<script>
		const evtSource = new EventSource("sse");
		
		evtSource.addEventListener("ping", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  const peer = JSON.parse(event.data).peer;
  newElement.textContent = `ping at ${time} from ${peer}`;
  eventList.appendChild(newElement);
});

		</script>
		<ul id="list">

		</ul>
	</body>
</html>

The code is based on the article Using server-sent events on MDN: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

Ingredient #2: server side code

The TIdHTTPServer subclass contains a method to provide client-specific data in the /sse resource. When invoked, it will return a single ping event with a JSON payload, containing the peer IP and port number, and a timestamp:

  function TMySSEServer.BuildContentText(
    AContext: TIdContext): string;
  begin
    Result := 'event: ping' + #13
      + Format('data: {"time": "%s", "peer": "%s:%d"}',
      [DateToISO8601(Now, False),
      AContext.Binding.PeerIP,
      AContext.Binding.PeerPort]) + #13#13;
  end; 

The DoCommandGet method uses the BuildContentText function to provide the event data, and simulates work by sleeping for a random time interval.

Note

  • The data stream is running in an endless loop (repeat ... until false).
  • Because the method never terminates, the method calls AResponseInfo.WriteHeader to send the HTTP headers to the client (line 13).
  • Neither ContentText nor ContentStream can be used to send data to the client. Instead, the event data must be sent by using the Write.. methods of the connection’s IOHandler (line 16).
 procedure TMySSEServer.DoCommandGet(AContext: TIdContext;
    ARequestInfo: TIdHTTPRequestInfo;
    AResponseInfo: TIdHTTPResponseInfo);
  var
    Data: string;
  begin
    AResponseInfo.CharSet := 'UTF-8';
    if ARequestInfo.Document = '/sse' then
    begin
      AResponseInfo.ContentType := 'text/event-stream';
      AResponseInfo.CacheControl := 'no-store';
      AResponseInfo.ContentLength := -2;
      AResponseInfo.WriteHeader;
      repeat
        Data := BuildContentText(AContext);
        AContext.Connection.IOHandler.Write(Data);
        Sleep(Random(1000));
      until False;
    end
    else
    begin
      AResponseInfo.ContentType := 'text/html';
      AResponseInfo.ContentStream := TFileStream.Create('index.html', fmOpenRead);
    end;
  end;

Output

When the browser navigates to http://localhost, the server will provide the HTML and the embedded JavaScript will start reading event data from http://localhost/sse:

Notable difference from the previous version:

  • The server sends a continuous stream of events as response to the HTTP GET request to the /sse resource.
  • The length of the response is unknown (it is virtually unlimited), therefore the HTTP response must not contain a content-length header.
  • The connection will not be closed after sending one or more events.
  • The client will only retry (reconnect and send a new request), if the the server disconnects its end of the connection, or no data is received and the connection times out.

Diagnostics

To see the full response of the server to the GET request, you may use

curl -v -N localhost/sse

Example:

Updates

Example project source code is now on GitHub

The complete code for all three projects is now available on GitHub at https://github.com/michaelJustin/indy-snippets

Example project for Daraja HTTP Framework

There is a new example project in the develop branch of Daraja. URL: https://github.com/michaelJustin/daraja-framework/tree/develop/demo/server-sent%20events

Advertisement

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s