Server-Sent Events (SSE) — Realtime

Server-Sent Events (SSE) SSE adalah one-way stream dari server ke client via HTTP. Simpler than WebSocket, built-in browser API, auto-reconnect. Server…

Server-Sent Events (SSE) adalah satu response HTTP yang gak pernah selesai: server terus menulis kabar ke dalamnya sebagai baris teks, dan EventSource di browser membaca tiap kabar jadi event. Bayangin pengeras suara dari ruang cuci ke meja depan. Mas Eko mengumumkan tiap perubahan dengan nomor: "Nomor 41: dua belas, disetrika." Orang di depan cuma bisa mendengar, gak bisa menjawab lewat pengeras suara itu. Kalau kabelnya copot, kabelnya dicolok lagi beberapa detik kemudian, dan Bu Rini bilang "yang terakhir kudengar nomor 41", jadi Mas Eko mengulang semua pengumuman sesudah 41.

🧪 Tebakan cepat: Koneksi SSE papan Bu Rini putus beberapa detik. Selama putus, Mas Eko mengubah #13 jadi Siap diambil. Setelah tersambung lagi, apakah papan tahu soal #13? Jawabannya ada di bagian "Putus dan sambung lagi: Last-Event-ID".

Format text/event-stream

Server menjawab dengan Content-Type: text/event-stream, lalu menulis event demi event tanpa menutup response-nya:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Access-Control-Allow-Origin: http://localhost:8000

: papan Laundry Bu Rini

event: status
data: {"id":12,"status":"Disetrika"}
id: 41

Baris Artinya
data: ... isi event. Beberapa baris data: berturut-turut digabung dengan baris baru (\n)
event: status nama event. Tanpa baris ini, namanya message
id: 41 nomor event. Browser mengingatnya sebagai Last-Event-ID
retry: 2000 jeda reconnect yang diminta server, dalam milidetik
: teks komentar, diabaikan browser. Dipakai sebagai heartbeat supaya koneksi gak dianggap diam
baris kosong event selesai: kirim ke browser sekarang

Satu spasi sesudah titik dua dibuang, field yang gak dikenal diabaikan, dan isinya selalu teks UTF-8. Event status di atas 59 byte, dan di kabel HTTP/1.1 jadi 65 byte (ditambah penanda panjang potongan). Kalau server menjawab dengan Content-Type lain, misalnya application/json, EventSource langsung menyerah: diuji di ketiga browser, cuma satu request dan gak ada reconnect.

Server: Node.js tanpa library

import http from 'node:http'

const layar = new Set() // response yang masih terbuka
const riwayat = []      // event terakhir, untuk dikirim ulang
let nomor = 40

http.createServer((req, res) => {
  if (req.url !== '/papan/stream') return res.writeHead(404).end()
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Access-Control-Allow-Origin': 'http://localhost:8000',
  })
  const terakhir = req.headers['last-event-id']
  if (terakhir) for (const e of riwayat) if (e.id > Number(terakhir)) res.write(e.teks)
  layar.add(res)
  req.on('close', () => layar.delete(res))
}).listen(3000)

// Dipanggil saat Mas Eko mengubah status
function kirimStatus(id, status) {
  nomor++
  const teks = `event: status\ndata: ${JSON.stringify({ id, status })}\nid: ${nomor}\n\n`
  riwayat.push({ id: nomor, teks })
  if (riwayat.length > 100) riwayat.shift()
  for (const res of layar) res.write(teks)
}

setInterval(() => {
  for (const res of layar) res.write(': ping\n\n') // heartbeat
}, 25000)

EventSource di browser

const sumber = new EventSource('http://localhost:3000/papan/stream')

sumber.addEventListener('status', (event) => {
  const { id, status } = JSON.parse(event.data)
  document.querySelector(`#status-${id}`).textContent = status
  console.log('event nomor', event.lastEventId) // "41"
})
sumber.onopen = () => console.log('tersambung')
sumber.onerror = () => console.log('putus, readyState', sumber.readyState)

Dibanding WebSocket, SSE lebih simpel untuk push satu arah: cuma HTTP biasa, gak butuh protokol atau library khusus, EventSource sudah ada di browser, dan reconnect-nya otomatis.

Putus dan sambung lagi: Last-Event-ID

Kalau koneksinya putus, EventSource menyambung lagi sendiri, dan request barunya membawa header Last-Event-ID berisi nomor event terakhir yang dia terima. Diuji di Chromium 147:

Waktu Yang terjadi
0 dtk papan menerima event 41: #12 Disetrika
1 dtk proxy memutus koneksi. Event error, readyState 0
2 dtk Mas Eko mengubah #13 jadi Siap diambil. Server menyimpan event 42 di riwayat
4 dtk browser menyambung lagi (3 detik setelah putus) dengan header Last-Event-ID: 41
4 dtk server mengirim ulang event 42, papan menampilkan #13 Siap diambil

Jadi papan tahu, asal server menulis id: dan mengirim ulang event sesudah Last-Event-ID. Itu jawaban tebakannya. Tanpa id:, browser gak mengirim Last-Event-ID, server gak tahu apa yang terlewat, dan #13 tetap salah sampai ada kabar berikutnya.

Jeda reconnect-nya diukur: Chromium 147 dan Safari (WebKit 26.4) menunggu 3 detik, Firefox 146 menunggu 5 detik. Baris retry: 2000 dari server mengubahnya jadi sekitar 2 detik di ketiganya. Untuk menghentikan reconnect dari server, jawab dengan 204 No Content: EventSource berhenti di readyState 2.

Yang gak bisa dilakukan EventSource

⚠️ Jebakan yang sering ketemu

TL;DR: SSE adalah satu response Content-Type: text/event-stream yang terus terbuka. Tiap event berupa baris data: (beberapa baris digabung dengan \n), opsional event: (tanpa itu namanya message), id:, dan retry:, ditutup baris kosong. Baris berawalan : adalah komentar untuk heartbeat. EventSource menerima event bernama lewat addEventListener, menyambung ulang sendiri (3 detik di Chromium dan Safari, 5 detik di Firefox, atau sesuai retry:), dan mengirim Last-Event-ID supaya server bisa mengirim ulang yang terlewat. Server menghentikannya dengan 204. EventSource gak bisa memasang header sendiri, gak bisa POST, cuma satu arah dan teks, dan lintas origin butuh CORS (dengan withCredentials, origin harus disebut persis). Untuk push satu arah, SSE lebih simpel dari WebSocket: HTTP biasa, API bawaan, reconnect otomatis.

Yang akan kamu pelajari