Real-Time Protocols

Server-Sent Events (SSE) Implementation Guide

How to implement Server-Sent Events for real-time push: the EventSource API, server-side streaming, automatic reconnection, and scalability.

What Are Server-Sent Events?

Server-Sent Events (SSE) is an HTTP-based mechanism for one-directional real-time streaming from server to client. A client opens a long-lived HTTP connection, and the server continuously pushes events down the wire.

SSE is part of the HTML5 specification (WHATWG) and works natively in every modern browser via the EventSource API — no library required.

SSE vs WebSocket

SSEWebSocket
DirectionServer → Client onlyFull duplex
ProtocolHTTP/1.1 or HTTP/2Upgraded TCP
ReconnectionAutomaticManual
Proxy/firewallEasy (plain HTTP)Sometimes blocked
Browser API`EventSource``WebSocket`
Use caseNotifications, feedsChat, games, collaboration

Choose SSE when you need server-to-client push without bidirectional communication. For bidirectional real-time, use WebSockets.

The EventSource API

const source = new EventSource('/api/events');

// Default message event
source.onmessage = (event) => {
  console.log('Received:', event.data);
};

// Named event type
source.addEventListener('order-update', (event) => {
  const order = JSON.parse(event.data);
  updateOrderUI(order);
})

source.onerror = (event) => {
  if (source.readyState === EventSource.CLOSED) {
    console.log('Connection closed');
  }
};

Server Implementation

Python (Django/FastAPI):

from django.http import StreamingHttpResponse
import json, time

def event_stream(request):
    def generate():
        while True:
            data = get_latest_data()  # your data source
            yield f'event: update\n'
            yield f'data: {json.dumps(data)}\n'
            yield f'id: {int(time.time())}\n'
            yield '\n'  # blank line = end of event
            time.sleep(1)
    return StreamingHttpResponse(
        generate(),
        content_type='text/event-stream',
        headers={
            'Cache-Control': 'no-cache',
            'X-Accel-Buffering': 'no',  # disable Nginx buffering
        },
    )

Node.js (Express):

app.get('/api/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify(getUpdate())}\n\n`);
  }, 1000);

  req.on('close', () => clearInterval(interval));
});

Event Types and IDs

The SSE format supports four fields per event:

event: order-update\n       ← named event type (optional)
data: {"id": 42, ...}\n     ← event payload
id: 1710000042\n            ← event ID for reconnection
retry: 5000\n               ← reconnection delay in ms
\n                          ← blank line terminates the event

Multi-line data: use multiple data: lines; the client joins them with newlines.

Automatic Reconnection

If the connection drops, the browser's EventSource automatically reconnects after 3 seconds (default) or the value from the last retry: field.

On reconnect, the browser sends a Last-Event-ID header with the last received event ID. Your server should use this to resume from where the client left off:

def event_stream(request):
    last_id = request.headers.get('Last-Event-ID')
    events = get_events_since(last_id)  # replay missed events
    ...

Scalability Considerations

Each SSE connection holds an open HTTP connection. With 10,000 concurrent users, that is 10,000 open file descriptors.

  • Use async workers (asyncio, Node.js) rather than synchronous workers — one thread per connection does not scale
  • With Nginx, set X-Accel-Buffering: no to prevent Nginx from buffering the stream
  • Over HTTP/2, multiple SSE streams share a single connection — better utilization than HTTP/1.1
  • For very high connection counts, use a dedicated message broker (Redis Pub/Sub, Kafka) between your app and the SSE endpoint

Protokol Terkait

Istilah Glosarium Terkait

Lebih lanjut di Real-Time Protocols