Episode ini membangun komunikasi realtime: WebSocket dengan gorilla/websocket di handler Gin, Server-Sent Events dengan SSEvent, streaming upload dan download, serta use case notifikasi dan chat.

Sampai episode 15, pola komunikasi kita selalu request-response: klien bertanya, server menjawab. Episode 16 ini membuka arah baru: realtime. Kalian akan membedah WebSocket dengan gorilla/websocket di dalam handler Gin, Server-Sent Events (SSE) untuk notifikasi satu arah, streaming upload dan download, serta use case nyata seperti chat dan progress bar.
Mengapa penting? Aplikasi modern menuntut pembaruan langsung: notifikasi muncul tanpa refresh, chat terkirim dalam milidetik, dan progress upload tampak bergerak. WebSocket memberikan saluran dua arah, SSE memberikan saluran satu arah yang lebih sederhana, dan keduanya berjalan mulus di atas Gin.
# dua arah, latensi rendah, duplex
websocket: chat, game, kolaborasi
# satu arah, sederhana, auto-reconnect
sse: notifikasi, feed harga, progressUntuk sebagian besar notifikasi, SSE sudah cukup dan menghindari kompleksitas manajemen koneksi WebSocket di sisi klien.
Mulai dengan menginstall gorilla/websocket:
go get github.com/gorilla/websocketvar upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func echoHandler(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
break
}
}
}
r.GET("/ws", echoHandler)upgrader.Upgrade(c.Writer, c.Request, nil) mengubah request HTTP menjadi koneksi WebSocket. Handler ini memantulkan (echo) setiap pesan yang diterima. Perhatikan CheckOrigin — di production, kembalikan false untuk domain tak dikenal demi mencegah koneksi lintas situs.
Untuk menyebarkan pesan ke banyak klien, gunakan pola broadcast via channel:
var broadcast = make(chan []byte, 256)
func reader(conn *websocket.Conn) {
defer conn.Close()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
broadcast <- msg
}
}
func writer(conn *websocket.Conn) {
for msg := range broadcast {
if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
return
}
}
}broadcast <- msg mengirim pesan ke channel yang dibaca semua writer. Setiap koneksi punya goroutine reader dan writer; writer menerima pesan dari channel bersama. Di production, tambahkan registry klien dan filter per ruangan, dan selalu pasang deadline read/write untuk membuang koneksi yang diam.
Gin menyediakan c.SSEvent untuk menulis satu event:
func sseHandler(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Stream(func(w io.Writer) bool {
c.SSEvent("message", "tick pada " + time.Now().Format("15:04:05"))
time.Sleep(1 * time.Second)
return true
})
}
r.GET("/events", sseHandler)c.SSEvent("message", data) menulis event SSE dengan nama message. c.Stream menjalankan fungsi secara berulang selama mengembalikan true — ini pola streaming satu arah yang dibahas singkat di episode 7. Header text/event-stream memberi tahu browser bahwa respons akan bertahan lama.
File besar tidak perlu dimuat seluruhnya ke memori. Stream langsung dari body request ke file:
func streamUploadHandler(c *gin.Context) {
src := c.Request.Body
defer src.Close()
dst, err := os.Create("./storage/besar.bin")
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer dst.Close()
n, err := io.Copy(dst, src)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"bytes": n})
}io.Copy(dst, src) memindahkan data dari request ke disk dengan buffer kecil, bukan menampung seluruh body. Kombinasikan dengan Content-Length untuk membatasi ukuran dan dengan middleware rate limit dari episode 14 agar upload tidak menyedot bandwidth.
Contoh paling umum SSE adalah notifikasi: server mengirim event setiap kali ada perubahan status. Server tidak perlu menunggu klien bertanya — event didorong langsung saat status berubah, misalnya setelah order dibuat atau pembayaran diverifikasi. Implementasinya cukup endpoint SSE yang membaca channel status, persis pola broadcast di atas.
Untuk chat, pilih WebSocket karena membutuhkan komunikasi dua arah dengan latensi rendah. Setiap pesan masuk dibroadcast ke semua koneksi di ruangan yang sama. Karena model ini berjalan di banyak goroutine, terapkan disiplin dari episode 12: gunakan channel untuk berbagi data, mutex untuk registry klien, dan selalu tutup goroutine saat koneksi berakhir agar tidak bocor.
Inti yang harus dibawa pulang:
upgrader.Upgrade mengubah handler Gin menjadi koneksi WebSocket.c.SSEvent dan c.Stream membangun endpoint SSE dalam beberapa baris.io.Copy dari c.Request.Body memungkinkan streaming upload.CheckOrigin, deadline, dan batas ukuran untuk keamanan koneksi.Di episode 17 selanjutnya kita akan membedah testing & benchmark — unit test handler dengan httptest, gin.CreateTestContext, table-driven test, mocking service, serta benchmark route dengan testing.B untuk membandingkan alokasi dan latensi.