Belajar Server Sent Event (SSE) dan Tutorial SSE dengan Go dan React (Part 1 : Basic SSE with Auth)

Gazandi Cahyadarma
8 min readApr 25, 2021

--

Dalam part 1, kalian akan belajar:

1. Dasar, Pengertian, dan Manfaat SSE
2. Membuat basic SSE dengan backend dan frontend terpisah,
3. Mengamankan SSE dengan credential.

Ini adalah tulisan pertamaku dari hasil pembelajaran book club: 1 chapter for a week yang sedang aku ikuti bersama sahabat-sahabatku. Buku yang sedang kami baca adalah buku berjudul “High Performance Browser Networking”. Kami memutuskan mengambil bab Server Sent Event karena bab ini merupakan hal baru bagi kami. Pembahasan book club ini tidak hanya kami baca saja, kami juga praktekkan hingga ada contoh aplikasinya lho, jadi yuk ikutin tulisan ini sambil mencoba membuat server sent event.

Sebelum membahas Server Sent Event, kita akan belajar tentang HTTP (Hypertext Transfer Protocol) terlebih dahulu. HTTP adalah dasar komunikasi pada World Wide Web (www). HTTP membuat kita bisa membuka website yang kita inginkan, seperti medium, sosial media, hingga belajar online.

Pada tahun 2021, HTTP yang dipakai ada 3 jenis: HTTP 1.x, HTTP/2, dan HTTP/3. HTTP yang akan kita bahas adalah HTTP 1.x yang menggunakan protokol TCP (baca protokol TCP disini). Gimana sih cara kerja HTTP ? Yuk kita cermati bagan cara kerja HTTP di bawah ini:

Bagan cara kerja HTTP 1.1x (sumber)

Cara kerja HTTP 1.x harus mengikuti kesepakatan internasional RFC 2616 dan RFC793 yang menjadikan flownya seperti ini:

  1. Client mengirimkan paket SYN (bagian dari three way handshake TCP) yang mengambil nilai random x dan mengirimkannya beserta flag dan opsi TCP.
  2. Server mengakui/meng-ACK paket SYN yang dikirimkan (bagian dari three way handshake TCP). Nilai random x ditambahkan dengan satu (increment) dan mengambil nilai random y untuk dikirimkan ke client.
  3. Client mengakui paket yang dikirimkan dari server dengan nilai random y untuk ditambah dengan satu (bagian dari three way handshake TCP).
  4. Setelah itu, baru Client meminta data seperti HTML, CSS, JSON, atau gambar untuk diproses menjadi sebuah website yang dapat dilihat oleh manusia.

Untuk beberapa kasus, flow three way handshake TCP pada HTTP ini dianggap berlebihan. Salah satu studi kasusnya adalah push notification, bayangkan kita harus melakukan berulang kali pengiriman paket SYN, SYN ACK, ACK, dan request data setiap saat untuk melakukan cek ada notifikasi baru atau tidak. Tidak efisien kan ? Bisa ga ya kalau dari server yang ngirim aja ?

Berawal dari kebutuhan mengirimkan data dari server, HTTP mengadakan support stream text dari server yang bernama Server-Sent-Event pada RFC6202. Flow SSE ini bisa dilihat pada dibawah ini:

Bagan cara kerja Server Sent Event. (sumber)

Flow pada bagan diatas membuat HTTP hanya melakukan three way handshake dan request data sekali hingga client/server menutup koneksi. Apa sih yang perlu dilakuin untuk menggunakan SSE ini ?

  1. Baca Event Stream Format dan desain komunikasi SSE yang kalian inginkan. Apakah akan model batch/single dan mempunya id/type apa. Karena data yang berbeda akan mempengaruhi cara olahnya. Format menggunakan \n\n diakhir agar tahu perbedaan batch dan single di tiap message. Ketika sudah ada dua enter (\n\n), akan dianggap message yang berbeda.
  2. Menyediakan server yang menulis ke response pada sebuah loop hingga data yang dikirimkan habis (terputus dari server), atau terputus dari client. Kita akan menggunakan header khusus pada response untuk memastikan SSE ini berjalan yaitu: response headerContent-Type: text/event-stream untuk melakukan stream dan response headerConnection: keep_aliveuntuk memastikan koneksi hidup akan selalu tersambung. Kita cukup membuat handler yang menggunakan loop for true, lalu jika terputus koneksi/data habis akan melakukan return/break.
  3. Kita dapat menambahkan header khusus pada requestAccept: text/event-stream untuk memberi tahu ke HTTP Server bahwa ini menggunakan cara SSE. Lalu membuat stream client sendiri pada client. Atau kita bisa menggunakan mekanisme EventSource API (SSE client) yang sudah ada untuk mendapatkan response server secara stream. Beberapa browser sudah mempunyai mekanisme ini. Kalian bisa melihat dokumentasinya disini atau cukup dengan melihat dibawah ini:

// Membuat koneksi SSE baru dengan input stream endpoint
var source = new EventSource(“/path/to/stream-url”);

// Optional callback, dijalankan ketika koneksi dibuat
source.onopen = function () { … };

// Optional callback, dijalankan ketika koneksi gagal
source.onerror = function () { … };

// Membuat callback khusus ketika menerima event “foo”

source.addEventListener(“foo”, function (event) {
processFoo(event.data);
});

// Callback yang dijalankan ketika menerima semua event
source.onmessage = function (event) {
log_message(event.id, event.data);

if (event.id == “CLOSE”) {
// menutup koneksi dari client.

source.close();
}

Bagaimana jika ingin menggunakan EventSource dari android/ios client ? Temen2 disini bisa googling seperti link berikut yaa.

Membuat basic SSE dengan backend dan frontend terpisah

Menurut teori, SSE bisa dibuat dengan membuat server SSE dan menggunakan mekanisme Event Source pada client. Kali ni, kita akan bahas gimana sih ngoding SSE di Server dan Client.

Saatnya kita buat basic SSE dahulu, langkah2nya sebagai berikut
1. Menyiapkan dua repl repository, backend dengan tech stack go, dan frontend dengan tech stack react.

2. Buatlah handler function yang menulis ke response pada sebuah loop hingga data yang dikirimkan habis (terputus dari server), atau terputus dari client. Kita akan menggunakan header khusus SSE untuk case frontend dan backend yang terpisah. Beberapa yang harus diperhatikan pada saat membangun Backend SSE:

  1. Pastikan gunakan double enter untuk fmt.Fprintf(w, “data: %d\n\n”, m) karena itu format SSE yang bisa diterima sebagai penanda berakhirnya sebuah event message.
  2. Jangan menggunakan wildcard pada `Access-Control-Allow-Origin` karena akan membuat SSE backend tidak dapat diterima oleh client.
  3. Jangan membuat infinity loop yang tidak ada break/returnnya sama sekali.
  4. Tidak disarankan untuk binary streaming atau UTF-8 data. Dibuat untuk stream data teks. Baca lebih lanjut
  5. Jika batere user jadi concern, penggunaan format batch dan penghapusan keep-alive disarankan.
func handleSSE() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Get handshake from client")
// prepare the header
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
// set header keep-alive agar koneksinya tidak terputus
w.Header().Set("Connection", "keep-alive")
// set header untuk allow CORS request (perlu diubah)
w.Header().Set("Access-Control-Allow-Origin", "https://[SSE-client-url]")
w.Header().Set("Access-Control-Allow-Credentials", "true")
defer func() {
log.Printf("client connection is closed")
}()
// prepare the flusher
flusher, _ := w.(http.Flusher)
m := 0
// loop forever dengan return
for {
select {
// ketika koneksi terputus, fungsi defer bakal jalan
case <-r.Context().Done():
return
default:
/// retrieve data
m++
log.Printf("print message to client", m)
fmt.Fprintf(w, "data: %d\n\n", m)
flusher.Flush()
time.Sleep(2000 * time.Millisecond)
if m >= 100000 {
return;
}
}
}
}
}

kira2 lengkapnya akan menjadi seperti ini :

package mainimport (
"fmt"
"log"
"time"
"net/http"
)
func handleSSE() http.HandlerFunc {
// isinya diatas
}
func main() {
http.HandleFunc("/stream", handleSSE())
log.Fatal("HTTP server error: ", http.ListenAndServe("0.0.0.0:3000", nil))
}

3. Menggunakan eventSource pada react. Kita akan menggunakan fungsi useEffect() pada react dalam function App() sebelum return.
Dengan menggunakan EventSource API pada javascript, file jsnya akan menjadi seperti ini:

useEffect(() => {
const sse = new EventSource('https://[SSE-API-URL]');
function getRealtimeData(data) { console.log(data)
// process the data here,
// then pass it to state to be rendered
}
sse.onopen = () => {
console.log("opening")
}
sse.onmessage = e => getRealtimeData(e.data);
sse.onerror = (err) => {
// error log here
console.log("Error")
console.log(err)
sse.close();
}
return () => {
console.log("closing")
sse.close();
};
});

kira2 lengkapnya akan menjadi seperti ini :

import React, {useEffect} from 'react';import './App.css';function App() {useEffect(() => {
//isinya diatas
);
return (
<main>
React⚛️ + Vite⚡ + Replit🌀
</main>
);
}
export default App;

4. Kita uji yuk kodingannya. Seharusnya aplikasi kalian akan mendapatkan log seperti ini:

Pengujian Basic SSE

Mengamankan SSE dengan credential.

Setelah basic SSE selesai, kami ada pertanyaan, apakah bisa membedakan User A dan User B ? kita tidak bisa hanya mengandalkan limitasi CORS untuk keamanan.

Event source ternyata memiliki opsi withCredentials saat menjalankan constructornya. Opsi ini akan membuat Event Source menginisiasi stream dengan cookies, Ada dua hal yang dapat dilakukan untuk membedakan user yang ada:

  1. Tambahkan cookies checker setelah set header dan sebelum fungsi defer di fungsi handleSSE. Cookie bisa diambil dari access token yang kalian punya, lalu melakukan hit ke authentication service. Pada kasus tutorial ini, kami menggunakan pengecekan dengan hardcode.
// w.Header().Set("Access-Control-Allow-Credentials", "true")

// tambahkan setelah fungsi set header, copy mulai dari sini
// Read cookie
cookie, err := r.Cookie("auth")
if err != nil {
fmt.Fprintf(w, "event:l\ndata: %s\n\nevent:l\ndata: %s\n\n", "can't find cookie")
fmt.Printf("Cant find cookie : %s\n", r.Cookies())
return
}
fmt.Printf("%s=%s\r\n", cookie.Name, cookie.Value)if cookie.Value != "cookie" {
fmt.Fprintf(w, "data: %s\n", "Unauthorized cookie")
fmt.Printf("Unauthorized cookie :/\n\n")
return
}
// tambahkan sebelum fungsi defer, copy berhenti di sini// defer func() {
// log.Printf("client connection is closed")
// }()

2. Tambahkan opsi withCredentials pada evenrsource. sebelum eventsource, kami lakukan simulasi auth dengan melakukan setcookies secara manual. Teman2 bisa menggantinya dengan menembak API auth yang dipunya masing2. Sebelum ditambah:

// sebelum ditambahuseEffect(() => {
const sse = new EventSource('[SSE-stream-url]');

Sesudah ditambah:

//setelah diubahuseEffect(() => {  function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;domain=.[repl-username].repl.co";
}
setCookie("auth", "cookie", 1)
const sse = new EventSource('[SSE-stream-url]', { withCredentials: true });

Setelah melakukan adjustment, kami perlu mengetes autentikasi ini berjalan semestinya atau malah tambah kacau, untuk itu kita menguji dengan 2 cookies,

Kondisi ada dua cookies: yaitu auth dan authz. auth akan digunakan untuk pengecekan autentikasi. authz adalah cookie untuk pengecekan cookie dikirim melalui SSE atau tidak.

Setelah ditambah, kami melakukan beberapa pengujian:

1. cookie sama dengan pengecekan, ekspektasi: berjalan normal, menemukan dua cookie, realita: sesuai

2. cookie beda, ekspektasi: unauthorized cookies, realita: sesuai karena nilai auth tidak sama.

3. cookie tidak ada, ekspektasi: tidak menemukan cookie auth, menemukan hanya authz di server, realita: sesuai

4. opsi dihilangkan, ekspektasi: tidak menemukan cookie apapun di server, realita: sesuai

Dengan pengujian ini, kami membuktikan bahwa kedua step itu memang diperlukan.

Terima kasih sudah membaca dan mencoba, mohon maaf jika ada salah kata maupun kurang dimengerti. Kalau ada pertanyaan bisa komen yaa. Part selanjutnya: Production Grade SSE dengan resource planning dan benchmark.

Lihat codingan:
- Backend-link
- Frontend link

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response