Peristiwa yang dikirim server (SSE) mengirimkan pembaruan otomatis ke klien dari server, dengan koneksi HTTP. Setelah koneksi dibuat, server dapat memulai transmisi data.
Anda dapat menggunakan SSE untuk mengirim notifikasi push dari aplikasi web. SSE mengirim informasi dalam satu arah, sehingga Anda tidak akan menerima update dari klien.
Konsep SSE mungkin sudah tidak asing lagi. Aplikasi web "berlangganan" ke aliran update yang dibuat oleh server dan, setiap kali peristiwa baru terjadi, notifikasi akan dikirim ke klien. Namun, untuk benar-benar memahami peristiwa yang dikirim server, kita perlu memahami keterbatasan pendahulunya, AJAX. Hal ini mencakup:
Polling: Aplikasi berulang kali polling data di server. Teknik ini digunakan oleh sebagian besar aplikasi AJAX. Dengan protokol HTTP, pengambilan data berkaitan dengan format permintaan dan respons. Klien membuat permintaan dan menunggu server merespons dengan data. Jika tidak ada yang tersedia, respons kosong akan ditampilkan. Polling tambahan menghasilkan overhead HTTP yang lebih besar.
Long polling (Hanging GET/COMET): Jika server tidak memiliki data yang tersedia, server akan menahan permintaan terbuka hingga data baru tersedia. Oleh karena itu, teknik ini sering disebut sebagai "Hanging GET". Saat informasi tersedia, server akan merespons, menutup koneksi, dan prosesnya diulang. Dengan demikian, server terus merespons dengan data baru. Untuk menyiapkannya, developer biasanya menggunakan hack seperti menambahkan tag skrip ke iframe 'tak terbatas'.
Peristiwa yang dikirim server, telah dirancang dari awal agar menjadi efisien. Saat berkomunikasi dengan SSE, server dapat mendorong data ke aplikasi Anda kapan saja, tanpa perlu membuat permintaan awal. Dengan kata lain, update dapat di-streaming dari server ke klien saat terjadi. SSE membuka satu saluran satu arah antara server dan klien.
Perbedaan utama antara peristiwa yang dikirim server dan long polling adalah SSE ditangani langsung oleh browser dan pengguna hanya perlu memproses pesan.
Peristiwa yang dikirim server versus WebSocket
Mengapa Anda memilih peristiwa yang dikirim server daripada WebSocket? Pertanyaan bagus.
WebSockets memiliki protokol yang kaya dengan komunikasi dua arah dan full-duplex. Saluran dua arah lebih baik untuk game, aplikasi pesan, dan kasus penggunaan apa pun yang memerlukan pembaruan mendekati real-time di kedua arah.
Namun, terkadang Anda hanya memerlukan komunikasi satu arah dari server.
Misalnya, saat teman memperbarui status, ticker saham, feed berita, atau
mekanisme push data otomatis lainnya. Dengan kata lain,
update ke Database Web SQL sisi klien atau penyimpanan objek IndexedDB.
Jika Anda perlu mengirim data ke server, XMLHttpRequest
selalu menjadi teman.
SSE dikirim melalui HTTP. Tidak ada protokol atau penerapan server khusus yang perlu dilakukan. WebSocket memerlukan koneksi full-duplex dan server WebSocket baru untuk menangani protokol.
Selain itu, peristiwa yang dikirim server memiliki berbagai fitur yang tidak dimiliki WebSockets secara desain, termasuk koneksi ulang otomatis, ID peristiwa, dan kemampuan untuk mengirim peristiwa arbitrer.
Membuat EventSource dengan JavaScript
Untuk berlangganan aliran peristiwa, buat objek EventSource
dan teruskan URL streaming Anda:
const source = new EventSource('stream.php');
Selanjutnya, siapkan pengendali untuk peristiwa message
. Secara opsional, Anda dapat
memproses open
dan error
:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
Saat update dikirim dari server, pengendali onmessage
akan diaktifkan dan data baru akan tersedia di properti e.data
-nya. Bagian yang ajaib adalah
setiap kali koneksi ditutup, browser akan otomatis terhubung kembali ke
sumber setelah ~3 detik. Implementasi server Anda bahkan dapat memiliki kontrol atas
waktu tunggu penyambungan ulang ini.
Selesai. Klien Anda kini dapat memproses peristiwa dari stream.php
.
Format aliran peristiwa
Mengirim aliran peristiwa dari sumber adalah masalah dalam membuat
respons teks biasa, yang ditayangkan dengan Content-Type text/event-stream
,
yang mengikuti format SSE.
Dalam bentuk dasarnya, respons harus berisi baris data:
, diikuti dengan pesan Anda, diikuti dengan dua karakter "\n" untuk mengakhiri streaming:
data: My message\n\n
Data multibaris
Jika pesan lebih panjang, Anda dapat memisahkannya menggunakan beberapa baris data:
.
Dua atau beberapa baris berturut-turut yang dimulai dengan data:
diperlakukan sebagai
satu bagian data, yang berarti hanya satu peristiwa message
yang diaktifkan.
Setiap baris harus diakhiri dengan satu "\n" (kecuali baris terakhir, yang harus diakhiri
dengan dua). Hasil yang diteruskan ke pengendali message
Anda adalah satu string
yang digabungkan dengan karakter baris baru. Contoh:
data: first line\n
data: second line\n\n</pre>
Tindakan ini akan menghasilkan "baris pertama\nbaris kedua" di e.data
. Selanjutnya, seseorang dapat menggunakan
e.data.split('\n').join('')
untuk merekonstruksi pesan tanpa karakter "\n".
Mengirim data JSON
Menggunakan beberapa baris membantu Anda mengirim JSON tanpa merusak sintaksis:
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
Dan kemungkinan kode sisi klien untuk menangani streaming tersebut:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
Mengaitkan ID dengan peristiwa
Anda dapat mengirim ID unik dengan peristiwa aliran data dengan menyertakan baris yang dimulai dengan id:
:
id: 12345\n
data: GOOG\n
data: 556\n\n
Menetapkan ID memungkinkan browser melacak peristiwa terakhir yang diaktifkan sehingga jika koneksi ke server terputus, header HTTP khusus (Last-Event-ID
) akan ditetapkan dengan permintaan baru. Hal ini memungkinkan browser menentukan peristiwa mana yang sesuai untuk diaktifkan.
Peristiwa message
berisi properti e.lastEventId
.
Mengontrol waktu tunggu penyambungan ulang
Browser mencoba terhubung kembali ke sumber sekitar 3 detik
setelah setiap koneksi ditutup. Anda dapat mengubah waktu tunggu tersebut dengan menyertakan baris yang diawali dengan retry:
, diikuti dengan jumlah milidetik untuk menunggu sebelum mencoba terhubung kembali.
Contoh berikut mencoba menghubungkan kembali setelah 10 detik:
retry: 10000\n
data: hello world\n\n
Menentukan nama peristiwa
Satu sumber peristiwa dapat menghasilkan berbagai jenis peristiwa dengan menyertakan
nama peristiwa. Jika ada baris yang diawali dengan event:
, diikuti dengan nama unik untuk peristiwa, peristiwa akan dikaitkan dengan nama tersebut.
Di klien, pemroses peristiwa dapat disiapkan untuk memproses peristiwa tertentu tersebut.
Misalnya, output server berikut mengirimkan tiga jenis peristiwa, yaitu peristiwa 'message' generik, 'userlogon', dan 'update':
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
Dengan penyiapan pemroses peristiwa di klien:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
Contoh server
Berikut adalah implementasi server dasar di PHP:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
Berikut adalah implementasi serupa pada Node JS menggunakan pengendali Express:
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
Membatalkan streaming peristiwa
Biasanya, browser akan otomatis terhubung kembali ke sumber peristiwa saat koneksi ditutup, tetapi perilaku tersebut dapat dibatalkan dari klien atau server.
Untuk membatalkan streaming dari klien, panggil:
source.close();
Untuk membatalkan streaming dari server, respons dengan Content-Type
bukan text/event-stream
atau tampilkan status HTTP selain 200 OK
(seperti 404 Not Found
).
Kedua metode ini mencegah browser membuat ulang koneksi.
Sekilas tentang keamanan
Permintaan yang dihasilkan oleh EventSource tunduk pada kebijakan asal yang sama dengan API jaringan lain seperti pengambilan. Jika Anda perlu endpoint SSE di server agar dapat diakses dari origin yang berbeda, baca cara mengaktifkannya dengan Cross Origin Resource Sharing (CORS).