WebSocket untuk Notifikasi Real-Time
Di artikel Anatomi Protokol HTTP, kita belajar bahwa HTTP adalah protokol request-response: setiap percakapan selalu dimulai dari klien. Itu bagus untuk mengambil data, tapi jadi masalah saat Anda butuh notifikasi real-time — chat, live harga saham, atau pemberitahuan order baru yang masuk ke layar kasir. Bagaimana caranya server memberi tahu klien tanpa klien bertanya dulu?
Jawaban klasiknya adalah WebSocket: protokol yang membuka koneksi dua arah yang persisten antara klien dan server, sehingga data bisa mengalir ke dua arah kapan saja. Artikel ini membahas kenapa HTTP tidak cukup untuk real-time, perbandingan polling vs SSE vs WebSocket, cara kerja handshake, implementasi di Spring Boot memakai STOMP, jebakan-jebakan yang sering muncul di produksi, dan studi kasus notifikasi order.
Masalahnya: HTTP Tidak Bisa "Menelepon" Klien
Konteks dasar HTTP: klien mengirim request, server membalas response, lalu koneksi bisa ditutup. Server tidak pernah berinisiatif mengirim data. Konsekuensinya, untuk hal-hal yang berubah tanpa ditekan klien — pesan baru di chat, harga yang bergerak tiap detik, atau order masuk — klien tidak akan pernah tahu kecuali ia sendiri yang bertanya.
Coba bayangkan skenario notifikasi order baru di e-commerce. Saat pelanggan berhasil checkout, kasir/moderator harus melihat order itu muncul sendiri di layar. Dengan HTTP murni:
Klien (Kasir) Server
| |
| 1. GET /orders (apa ada yang baru?) |
|--------------------------------->|
| 2. "Tidak ada." 200 OK [] |
|<---------------------------------|
| ...menunggu beberapa detik... |
| |
| 3. GET /orders (apa ada yang baru?) |
|--------------------------------->|
| 4. "Tidak ada lagi." 200 OK [] |
|<---------------------------------|
Server hanya menjawab pertanyaan; ia tidak pernah menelepon klien. Padahal real-time justru butuh arah sebaliknya: server memanggil klien. Di sinilah kita butuh pendekatan baru.
Tiga Pendekatan untuk "Push" ke Klien
Ada beberapa cara untuk meniru real-time di atas HTTP, dan satu cara yang benar-benar berbeda. Masing-masing punya tradeoff.
Polling: Klien Bertanya Berulang-Ulang
Pendekatan paling sederhana: klien memakai setInterval untuk memanggil GET /orders tiap beberapa detik. Jika ada data baru, langsung dirender.
- Kelebihan: gampang, berjalan di mana saja, tidak butuh infrastruktur baru.
- Kekurangan: boros. Sebagian besar request menjawab "tidak ada data baru", tapi tetap memakan bandwidth, CPU server, dan beban database. Makin cepat interval-nya, makin "realtime"-nya, tapi makin mahal juga.
- Tradeoff besar: latensi maksimum = interval polling. Kalau poll tiap 5 detik, notifikasi bisa telat hingga 5 detik — dan di antara itu Anda membayar banyak request kosong.
Long Polling: Server Menahan Jawaban
Variasi polling: klien mengirim request, dan server menahan respons selama masih belum ada data baru (misalnya 30 detik). Begitu data datang (atau timeout), server membalas, dan klien langsung membuat request baru.
- Kelebihan: latensi rendah (data terkirim begitu tersedia), masih berjalan di atas HTTP biasa.
- Kekurangan: lebih rumit untuk ditangani (timeout, koneksi hang, race condition saat dua data datang bersamaan), dan tiap siklus tetap membuat request HTTP baru dengan overhead header. Server yang tahan banyak koneksi panjang ini juga harus diperlakukan khusus di level load balancer.
- Tradeoff: lebih hemat daripada polling murni, tapi bukan komunikasi dua arah yang alami — masih berupa request-response yang dipanjangkan.
SSE (Server-Sent Events): Satu Arah Lewat HTTP
SSE adalah API browser yang memakai satu koneksi HTTP panjang untuk mengalirkan data dari server ke klien saja. Klien tidak bisa mengirim data lewat koneksi yang sama.
- Kelebihan: sederhana, otomatis reconnect bawaan, jalan di atas HTTP (tidak perlu protokol baru), bisa lewat proxy/CDN.
- Kekurangan: satu arah — jika klien perlu mengirim pesan, harus lewat request HTTP biasa; koneksi HTTP punya batas jumlah (browser membatasi ~6 koneksi HTTP per host).
- Tradeoff: sempurna untuk notifikasi satu arah (berita, feed), tapi kurang cocok untuk chat atau game yang butuh dua arah.
WebSocket: Dua Arah Penuh
WebSocket adalah protokol terpisah yang berjalan di atas TCP. Sekali terhubung, klien dan server sama-sama bisa mengirim kapan saja tanpa memulai percakapan baru — inilah full-duplex.
| Pendekatan | Arah | Latensi | Overhead per pesan | Kompleksitas | Cocok untuk |
|---|---|---|---|---|---|
| Polling | Klien → server | Tinggi (sesuai interval) | Tinggi (HTTP baru tiap kali) | Rendah | Fitur yang jarang berubah |
| Long Polling | Klien → server (jawaban ditahan) | Rendah | Sedang (header HTTP tiap siklus) | Sedang | Fallback untuk browser lama |
| SSE | Server → klien | Rendah | Rendah (satu koneksi HTTP) | Rendah | Notifikasi satu arah, feed |
| WebSocket | Dua arah, kapan saja | Sangat rendah | Rendah (frame kecil, tanpa header HTTP) | Tinggi | Chat, game, kolaborasi, live harga |
Aturan praktis: gunakan pendekatan termurah yang cukup. Notifikasi sesekali → SSE. Percakapan dua arah yang sering → WebSocket. Dan jika Anda hanya butuh mengambil data berkala, polling biasa atau bahkan cache HTTP mungkin sudah jauh lebih masuk akal.
Apa itu WebSocket?
WebSocket adalah protokol komunikasi yang menyediakan koneksi persisten dan dua arah (full-duplex) di atas koneksi TCP. Berbeda dengan HTTP yang tiap pesan membawa header berat dan "lupa" sesi sebelumnya, WebSocket mempertahankan satu koneksi yang bisa dipakai bolak-balik tanpa henti.
Data dikirim dalam frame — unit data kecil dengan header minimal — sehingga overhead-nya jauh lebih ringan daripada request HTTP penuh. Protokol ini dijelaskan secara lengkap oleh dokumentasi WebSockets API di MDN, dan ini contoh membuka koneksi dari JavaScript:
const ws = new WebSocket("ws://api.example.com/ws");
ws.onopen = () => {
console.log("Koneksi terbuka");
ws.send("Halo dari browser!"); // kirim pesan
};
ws.onmessage = (event) => {
console.log("Data dari server:", event.data); // terima pesan
};
ws.onclose = () => {
console.log("Koneksi ditutup, coba reconnect nanti");
};
Handshake: Naik Kelas dari HTTP
Bagian yang membingungkan banyak orang: WebSocket bukan HTTP, tapi koneksi WebSocket dimulai sebagai request HTTP. Klien mengirim request HTTP biasa dengan header Upgrade: websocket. Jika server setuju, ia membalas 101 Switching Protocols, dan kedua pihak "naik kelas" dari HTTP ke WebSocket di atas TCP koneksi yang sama.
Klien Server
| 1. GET /ws (Upgrade: websocket, |
| Sec-WebSocket-Key: ...) |
|--------------------------------------->|
| 2. HTTP/1.1 101 Switching Protocols |
| Upgrade: websocket |
| Sec-WebSocket-Accept: ... |
|<---------------------------------------|
| ==== koneksi TCP tetap terbuka ==== |
| sekarang bicara pakai frame WebSocket |
| 3. frame "Halo server" ------------>|
| 4. frame "Halo klien" <------------ |
| 5. frame ... (bolak-balik, bebas) |
Detail handshake:
- Klien mengirim request
GETke endpoint WebSocket (misal/ws) dengan headerConnection: UpgradedanUpgrade: websocket, plusSec-WebSocket-Key(random string). - Server memvalidasi, lalu membalas status
101 Switching Protocolsdengan headerSec-WebSocket-Acceptyang dihitung dari key klien. - Sejak saat itu, protokol berganti: HTTP tidak dipakai lagi, komunikasi berjalan lewat frame WebSocket hingga salah satu pihak menutup koneksi.
Kenapa lewat HTTP? Karena infrastruktur web (port, firewall, proxy) sudah paham HTTP. Memulai handshake sebagai HTTP membuat WebSocket bisa melewati port
443yang sudah terbuka, dan server bisa memakai logika HTTP yang ada (misalnya autentikasi) sebelum melakukan upgrade.
HTTP vs WebSocket
| Aspek | HTTP | WebSocket |
|---|---|---|
| Arah komunikasi | Satu arah per request-response | Dua arah penuh, kapan saja |
| Koneksi | Dibuka dan ditutup per request (kecuali keep-alive) | Satu koneksi persisten untuk seluruh sesi |
| Overhead | Header besar di tiap pesan | Frame kecil, header minimal |
| State | Stateless (tiap request independen) | Stateful (koneksi menyimpan konteks) |
| Inisiatif | Selalu dimulai klien | Klien atau server bisa memulai kirim |
| Cocok untuk | REST API, ambil data, halaman web | Chat, notifikasi real-time, live harga |
| Autentikasi | Header Authorization mudah dibawa | Tidak otomatis — harus ditangani sendiri |
Implementasi di Spring Boot dengan STOMP
Di Java/Spring, memakai WebSocket mentah (lewat TextWebSocketHandler) itu mungkin, tapi Anda akan menghabiskan banyak waktu membuat routing pesan manual. Solusi yang umum di produksi adalah STOMP di atas WebSocket.
Apa itu STOMP?
STOMP (Simple Text Oriented Messaging Protocol) adalah protokol pesan sederhana berbasis teks yang berjalan di atas WebSocket. Ia menambahkan hal-hal yang WebSocket mentah tidak punya:
subscribe/unsubscribe— klien bisa "berlangganan" ke topik, dan server mengirim pesan ke semua subscriber topik itu.destination(alamat topik) — pesan dirutekan ke tujuan bernama, seperti"/topic/notifications".- Semantik publish-subscribe — mirip pola event bus, tapi di level jaringan.
Dengan kata lain, WebSocket memberi Anda "kabel"; STOMP memberi Anda "aturan lalu lintas" di atasnya.
Spring Boot menyediakan spring-boot-starter-websocket yang membawa Spring WebSocket + STOMP sekaligus. Struktur prefix yang umum dipakai:
| Prefix | Dipakai untuk | Arah | Contoh |
|---|---|---|---|
/app | Klien mengirim pesan ke server (diproses @MessageMapping) | Klien → server | /app/chat.send |
/topic | Server mengirim pesan broadcast ke semua subscriber | Server → klien | /topic/notifications |
/user | Server mengirim pesan khusus ke satu user | Server → klien | /user/{id}/queue/orders |
/queue | Pesan privat yang ditujukan per-user | Server → klien | /user/{id}/queue/orders |
Konfigurasi Broker Sederhana
Buat kelas konfigurasi yang mengimplementasikan WebSocketMessageBrokerConfigurer. Inti yang wajib dipahami: enableSimpleBroker untuk mengaktifkan broker in-memory sederhana, dan setApplicationDestinationPrefixes untuk menandai bahwa pesan ber-prefix /app adalah pesan aplikasi yang harus diproses controller.
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOriginPatterns("*");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
}
Yang terjadi:
addEndpoint("/ws")— endpoint handshake WebSocket. Klien menghubungi/wsuntuk "naik kelas" ke WebSocket.enableSimpleBroker("/topic", "/queue")— server punya broker in-memory untuk topik/topic/...(broadcast) dan/queue/...(per-user).setApplicationDestinationPrefixes("/app")— pesan dari klien dengan alamat/app/...diteruskan ke method@MessageMapping.setUserDestinationPrefix("/user")— prefix untuk tujuan per-user.
Mengirim Notifikasi ke Semua (Broadcast)
Setelah konfigurasi di atas, membuat notifikasi broadcast semudah memanggil SimpMessagingTemplate. Anda bisa memanggilnya dari mana saja — termasuk dari service setelah transaksi database sukses, bukan hanya dari controller WebSocket.
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final SimpMessagingTemplate messagingTemplate;
public NotificationService(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
public void broadcastAnnouncement(String message) {
NotificationPayload payload = new NotificationPayload("announcement", message);
messagingTemplate.convertAndSend("/topic/notifications", payload);
}
}
Klien berlangganan topik itu lewat STOMP:
// StompJS — pendekatan umum di browser
const socket = new WebSocket("ws://localhost:8080/ws");
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
stompClient.subscribe("/topic/notifications", (message) => {
const data = JSON.parse(message.body);
renderNotification(data);
});
});
Mengirim Notifikasi ke User Tertentu
Broadcast tidak selalu tepat. Notifikasi "order Anda berhasil dibuat" harus sampai ke satu user tertentu, bukan semua orang. Di sinilah tujuan per-user berperan. Ada dua cara:
Cara 1 — convertAndSendToUser dari service:
public void notifyUser(Long userId, NotificationPayload payload) {
// "/user/{userId}/queue/orders" -> alamat privat untuk user ini
messagingTemplate.convertAndSendToUser(
String.valueOf(userId),
"/queue/orders",
payload
);
}
Cara 2 — @MessageMapping dengan @SendToUser:
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendToUser;
import org.springframework.stereotype.Controller;
@Controller
public class OrderController {
@MessageMapping("/order.status")
@SendToUser("/queue/orders")
public OrderStatus statusRequest(Long orderId) {
return orderService.findStatus(orderId);
}
}
Di sisi klien, langganan per-user memakai alamat /user/queue/orders — Spring yang menerjemahkannya menjadi /user/{principalId}/queue/orders berdasarkan identitas yang terautentikasi:
stompClient.subscribe("/user/queue/orders", (message) => {
showNewOrder(JSON.parse(message.body));
});
Contoh Handler Server Sederhana
Kombinasi lengkap: klien mengirim pesan lewat /app/..., server memproses, lalu mem-broadcast hasilnya ke /topic/.... Pola ini adalah fondasi chat dan papan notifikasi.
@Controller
public class ChatController {
private final SimpMessagingTemplate messagingTemplate;
public ChatController(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
@MessageMapping("/chat.send")
public void handleChatMessage(ChatMessage message) {
// proses validasi / simpan ke database bila perlu
chatMessageService.save(message);
// kirim ke semua subscriber topik chat
messagingTemplate.convertAndSend("/topic/chat", message);
}
}
Satu method @MessageMapping dengan return value langsung juga bisa memakai @SendTo agar Spring mengirim hasilnya ke topik tertentu tanpa SimpMessagingTemplate:
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) {
return new Greeting("Halo, " + message.getName() + "!");
}
Keamanan Singkat: Membatasi Endpoint /ws
Koneksi WebSocket yang terbuka tanpa autentikasi adalah celah serius — siapa pun bisa berlangganan topik yang seharusnya privat. Dengan Spring Security, batasi endpoint handshake agar hanya user yang terautentikasi yang bisa terhubung, misalnya lewat WebSocketMessageBrokerConfigurer dengan SimpUserRegistry dan channel interceptor, atau filter di level HTTP.
Pendekatan paling mudah: pastikan handshake /ws (yang diawali sebagai request HTTP) melewati filter autentikasi, lalu di configureMessageBroker gunakan interceptor untuk memeriksa principal:
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.Message;
import org.springframework.security.core.Authentication;
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication auth = (Authentication) accessor.getUser();
if (auth == null) {
throw new IllegalStateException("Tidak ada autentikasi pada handshake");
}
}
return message;
}
});
}
Jika handshake ditolak, browser menerima status 403 Forbidden dan koneksi gagal terbuka — persis seperti request REST yang diblokir.
Dua lapis pengaman: lapis pertama memastikan hanya user terautentikasi yang bisa membuka koneksi (handshake). Lapis kedua memastikan user hanya bisa
subscribeke topik yang menjadi haknya (otorisasi per topik). Lapis kedua sering dilupakan dan berujung kebocoran data privat antar-user.
Jebakan & Praktik yang Wajib Diketahui
Autentikasi Tidak Otomatis di Browser
Koneksi WebSocket dari browser tidak membawa header Authorization seperti fetch. Headers HTTP hanya dipakai saat handshake, dan browser tidak mengizinkan Anda menetapkan header kustom pada new WebSocket(...). Solusi yang umum dipakai:
- Token di query parameter —
new WebSocket("wss://.../ws?token=" + jwt), lalu di server ekstrak dan validasi saat handshake (bisa lewatHandshakeInterceptor). Sederhana, tapi token bocor ke log server (query string sering tercatat). - Header STOMP — STOMP punya fase
CONNECTsendiri yang membawa header, jadi token bisa dikirim di headerCONNECTsetelah koneksi WebSocket terbuka. Ini lebih aman dan umum dipakai library seperti StompJS. - Cookie session — karena handshake adalah request HTTP, cookie yang sudah ada ikut terkirim; server bisa mengautentikasi dari cookie session.
Satu Koneksi = Satu Mahal
Tiap klien yang terhubung memegang satu koneksi TCP yang tetap terbuka, lengkap dengan memori untuk buffer dan state di server. Pada aplikasi single-instance hal ini masih wajar, tapi begitu klien ribuan dan instance Anda bertambah jadi beberapa, masalah muncul: broker in-memory (enableSimpleBroker) hanya hidup di satu instance.
Klien A ──► Instance 1 ──► broker in-memory (topik X)
Klien B ──► Instance 2 ──► broker in-memory (topik X) ✗ tidak tahu pesan
Pesan baru masuk ke Instance 1 → Klien B di Instance 2 tidak menerima
Klien yang terhubung ke Instance 2 tidak akan menerima pesan yang di-broadcast dari Instance 1. Solusinya: ganti broker in-memory dengan broker eksternal (RabbitMQ, ActiveMQ Artemis, atau Redis pub/sub) yang menjadi "otak" bersama semua instance. Di Spring, itu tinggal mengganti enableSimpleBroker dengan enableStompBrokerRelay.
Reconnect & Heartbeat
Koneksi WebSocket bisa putus — jaringan berubah, proxy timeout, server restart. Klien yang tidak menyiapkan reconnect akan diam saja sampai halaman di-refresh. Praktik yang umum:
- Reconnect otomatis di klien (StompJS punya
reconnectDelay, atau library sepertibackoff). - Heartbeat ping/pong — kedua sisi mengirim sinyal kecil secara berkala agar koneksi yang mati terdeteksi cepat, bukan menunggu timeout yang lama. Di STOMP Spring, atur
heartbeatsaat handshake atau diStompHeaderAccessor. - Tangani
onclosedi server — lepaskan resource, dan (jika diperlukan) catat putusnya koneksi untuk log atau membership.
Skala Horizontal: Simple Broker vs Broker Eksternal
| Aspek | Simple Broker (in-memory) | Broker Eksternal (RabbitMQ/ActiveMQ) |
|---|---|---|
| Jumlah instance | 1 saja | Banyak (horizontal scaling) |
| Komunikasi antar-instance | Tidak ada | Ada (melalui broker bersama) |
| Konfigurasi di Spring | enableSimpleBroker(...) | enableStompBrokerRelay(...) |
| Kemampuan pesan | Terbatas (ingat semua di memory) | Persistent queue, clustering, routing lanjutan |
| Kapan dipakai | Prototipe, aplikasi kecil | Produksi multi-instance, pesan harus dijamin |
Kapan JANGAN Pakai WebSocket
WebSocket bukan jawaban universal. Pertimbangkan lagi jika:
- Notifikasinya sesekali — satu email pengingat per hari cukup dikirim via SSE atau bahkan polling; tidak perlu koneksi permanen.
- Anda hanya butuh request-response biasa — ambil daftar user, buat order, update profil: tetap pakai HTTP REST. WebSocket menambah kompleksitas besar tanpa manfaat.
- Klien sangat beragam dan lama — dukungan WebSocket di browser sudah luas, tapi beberapa proxy/network korporat memblokir upgrade; SSE sebagai fallback sering menyelamatkan.
- Anda butuh reliabilitas transaksi ketat — WebSocket adalah saluran cepat tapi fire-and-forget; untuk pesan yang tidak boleh hilang, pertimbangkan menyimpan ke database dan memakai WebSocket hanya sebagai notifikasi.
Prinsip utama: gunakan teknologi sesederhana mungkin yang memenuhi kebutuhan. WebSocket dipakai bukan karena "keren", tapi karena ada kebutuhan dua arah yang persisten dengan latensi rendah.
Studi Kasus: Notifikasi Order Baru di E-Commerce
Mari satukan semua konsep dalam satu skenario: toko online tempat kasir/moderator harus melihat order baru muncul langsung di layar, plus pelanggan menerima konfirmasi status order.
- Pelanggan checkout → request
POST /api/ordersbiasa (tetap HTTP — ini request-response normal) → order tersimpan di database. - Service memanggil
SimpMessagingTemplatesetelah transaksi sukses:convertAndSendToUser(moderatorId, "/queue/orders", payload)→ notifikasi muncul di layar kasir.convertAndSend("/topic/orders.new", payload)→ dashboard ops yang menampilkan semua order live ikut ter-update.
- Kasir mengubah status order (misal "Sedang Diproses") → server memanggil
convertAndSendToUser(customerId, "/queue/orders", status)→ pelanggan yang berlangganan/user/queue/orderslangsung melihat status terbaru tanpa me-refresh halaman.
Alur lengkapnya kira-kira begini:
Pelanggan Server (Spring) Kasir / Moderator
│ │ │
│ POST /api/orders │ │
├──────────────────────►│ │
│ │ simpan ke DB │
│ ├──────────┐ │
│ │ │ convertAndSendToUser
│ │ ├────────────►│ (muncul di layar)
│ │ │ │
│ │ │ convertAndSend("/topic/orders.new")
│ │ ├────────────►│ (dashboard live)
│ │ │
│ convertAndSendToUser │ │
│◄──────────────────────┤ (status order) │
Kunci keberhasilannya: data sumber tetap di database (sumber kebenaran), WebSocket hanya saluran pengumuman. Jika koneksi putus, data tetap aman di database dan bisa diambil ulang lewat REST biasa — WebSocket tidak pernah dijadikan satu-satunya tempat penyimpanan.
Ringkasan
HTTP adalah protokol request-response yang tidak bisa mengirim data tanpa diminta — untuk itu kita butuh pendekatan lain. Polling sederhana tapi boros, long polling menahan respons tapi rumit, SSE cocok untuk satu arah di atas HTTP, dan WebSocket memberikan koneksi persisten dua arah dengan latensi rendah. Di Spring Boot, STOMP di atas WebSocket (@MessageMapping, @SendTo, SimpMessagingTemplate, prefix /app//topic//user) membuat routing pesan dan notifikasi per-user jadi mudah. Ingat tiga jebakan besar: autentikasi tidak otomatis di browser, broker in-memory tidak bisa dipakai multi-instance (butuh broker eksternal), dan koneksi harus ditangani reconnect-nya. Dan yang terpenting: pilih WebSocket hanya ketika kebutuhan dua arah yang persisten benar-benar ada — untuk selebihnya, SSE atau REST biasa sering kali sudah lebih dari cukup.
Lanjut membaca
- WebSockets API — MDN
- Server-sent events — MDN
- Spring WebSocket Reference
- WebSocket STOMP Reference (Spring)
- Anatomi Protokol HTTP — fondasi request-response yang menjadi titik tolak artikel ini
- Spring Security & SecurityFilterChain — cara mengamankan endpoint handshake
/ws - Resilient Microservices — jika aplikasi Anda berjalan multi-instance dan butuh broker eksternal