Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Server-Sent Events (SSE): A Conceptual Deep Dive

DZone 's Guide to

Server-Sent Events (SSE): A Conceptual Deep Dive

This article explores how SSE came to be, how it works under the hood, and why it’s rapidly being adopted by developers.

Free Resource

https://ik.imagekit.io/ably/ghost/prod/2019/06/2520x1260@3x.png?tr=w-1520

We’re rapidly heading toward an event-driven world of data streams and APIs. Server-Sent Events fills a specific niche in this new world: an open, lightweight, subscribe-only protocol for event-driven data streams. This article explores how SSE came to be, how it works under the hood, and why it’s rapidly being adopted by developers.

A Bit of Background

SSE is based on something called Server-Sent DOM Events, which was first implemented in Opera 9. The idea is simple: a browser can subscribe to a stream of events generated by a server, receiving updates whenever a new event occurs. This led to the birth of the popular EventSource interface, which accepts an HTTP stream connection and keeps the connection open while retrieving available data from it. The connection is kept open until closed by calling EventSource.close().

What Are Server-Sent Events (SSE)?

SSE is a standard describing how servers can initiate data transmission towards clients once an initial client connection has been established. It provides a memory-efficient implementation of XHR streaming. 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.

SSE is designed to use the JavaScript EventSource API in order to subscribe to a stream of data in any popular browser. Through this interface, a client requests a particular URL in order to receive an event stream. SSE is commonly used to send message updates or continuous data streams to a browser client.

In a nutshell, a server-sent event is when updates are pushed (rather than pulled, or requested) from a server to a browser.

How Does SSE Work?

A connection over SSE typically begins with client-initiated communication between client and server. The client creates a new JavaScript EventSource object, passing the URL of an endpoint to the server over a regular HTTP request. The client expects a response with a stream of event messages over time.

The server leaves the HTTP response open until it has no more events to send, it decides that the connection has been open long enough and can be considered stale or until the client explicitly closes the initial request.

How SSE works from connection request to close.

How SSE works from connection request to close.

Here’s a quick example of opening a stream over SSE:

var source = new EventSource('URL_TO_EVENT_STREAM');
source.onopen = function() {
  console.log('connection to stream has been opened');
 };
source.onerror = function (error) {
  console.log('An error has occurred while receiving stream', error);
 };
source.onmessage = function (stream) {
  console.log('received stream', stream);
};

Why Would You Use SSE?

Ideally, when requesting data from a server, a simple XMLHttpRequest will do. However, there are scenarios where you’d want to keep the connection open using XHR streaming. But this brings its own set of overheads, including handling parsing logic and connection management.

This is where SSE comes in. SSE provides a layer of connection management and parsing logic, allowing us to easily keep the connection open while a server pushes new events to the client as they become available.

When Would You Use SSE?

The nature of realtime messaging and streaming data mean different protocols serve different purposes better than others. For multiplexed, bidirectional streaming WebSockets is perfect. For IoT devices with limited battery life, MQTT is more suitable. But sometimes these are overkill.

SSE is perfect for scenarios such as:

  • When an efficient unidirectional communication protocol is needed that won’t add unnecessary server load (which is what happens with long polling)
  • When you need a protocol with a predefined standard for handling errors
  • When you want to use HTTP-based methods for realtime data streaming
  • When you need a unidirectional protocol with better latency for users tha other HTTP-based ways of streaming data

Here are a few examples where SSE is already in use:

  • Subscribing to a feed of cryptocurrency or stock prices
  • Subscribing to a Twitter feed
  • Receiving live sports scores
  • News updates or alerts

Things to Consider With SSE

As with all things, SSE has its own challenges.

The major limitation of SSE is that it’s unidirectional so there’s no way to pass information to a server from a client. The only way to pass additional information is at the time of connection, which many developers choose to do with query strings. For example, if a stream URL is http://example.com/sse, developers can add a query string to pass information such as a user id http://example.com/sse?userid=891.

This unidirectionality causes an additional problem: when a client loses a connection there’s no reliable way to let the server know as there’s no way to perform client-to-server heartbeats. As SSE is based on TCP/IP, there is a mechanism that alerts a server when a client loses a connection. But it doesn’t always work well so the server doesn’t always immediately realize a connection is lost. However, this is a minor limitation to SSE.

SSE Support

SSE is widely supported across popular browsers, which means it’s supported by a range of mobile and embedded IoT devices. That said, before attempting to implement SSE, it’s worth checking support coverage for browsers, your application, or service supports.

Browers that support SSE as of June 2019.Browers that support SSE as of June 2019.

It’s also worth checking EventSource support. See the example below using polyfill:

if ('EventSource' in window) {
  // use polyfills
}
var source = new EventSource('URL');

A great example of polyfill for EventSource is Yaffle’s EventSource polyfill.

Using SSE at Scale

Since SSE is based on the HTTP protocol, scaling can be achieved with means such as load-balancing. However, you’d need to ensure some sort of shared resource behind the servers so they’re all kept in sync with new event updates.

Many choose to handle this with the Publish/Subscribe architecture design pattern. With pub/sub, events aren’t sent directly to clients but instead are sent to the broker. The broker then sends the message to all subscribers (which may include the original sender), and they send it to the clients. To host your own pub/sub mechanism, you could consider using Redis.

If you’d rather offload this complexity of scaling and focus on your core product engineering, you can rely on realtime messaging platforms that offer event-stream and SSE endpoints. Check out this detailed explanation of how to implement SSE using a real-time messaging platform that supports the protocol.

How to Use Server-Sent Events in the Best Way Possible

While SSE is easy to implement, oftentimes, developers tend to get stuck while sending data. SSE represents data in an actual stream, so should we need to send JSON with SSE, it looks something like this:

res.write('data: {\n');
res.write('data: "foo": "bar",\n');
res.write('data: "baz", 555\n');
res.write('data: }\n\n');

For developers who are used to writing pure JavaScript, when implementing SSE, they might end up with invalid JSON while parsing on the client side. Using a library that abstracts this with plain JavaScript can help. For example, using the express-sse library when using Express as your web framework. Here’s an example of SSE from the server using Node’s Express:

app.get('/stream', (req, res)=>{
  res.status(200).set({
    "connection": "keep-alive",
    "content-type": "text/event-stream"
  });
  res.write(`data: Hello there \n\n`);
});

Looking at the code above, notice three things:

  • Text/event-stream content type header. This is what browsers look for to confirm an event stream.
  • Keep-alive header. This tells the browser not to close the connection.
  • The double new-line characters at the end of data. This indicates the end of the message.

However, looking at the example above, it’s only useful when sending one event or using the setInterval function to continuously check the database for new connections.

If we were to use the express-sse library, for example, it exposes a method where we can emit to the stream from anywhere in our app, and not necessarily the event stream route.

var SSE = require('express-sse');
var sse = new SSE();
app.get('/stream', sse.init);
// we can always now call sse.send from anywhere the sse variable is available, and see the result in our stream.
let content = 'Test data at ' + JSON.stringify(Date.now());
sse.send(content);

Server-Sent Events Support

While SSE is a lightweight protocol from both an implementation and usage standpoint, there are still infrastructure considerations when operating event-driven streams at scale. And as we continue to move toward a world of event-driven data streams, apps, services, and APIs, SSE won’t always be the right choice of protocol or fulfill your streaming needs. Now more than ever, building on a platform that can futureproof your infrastructure needs is something even the tech giants know is the best choice.

References

Topics:
realtime data ,realtime communication ,data streaming ,data streams ,protocol ,server-sent events ,sse ,integration ,apis

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}