Middleware WebSocket
Referensi: Dokumentasi API Deno upgradeWebSocket
Middleware WebSocket menangani upgrade koneksi WebSocket, memungkinkan komunikasi dua arah real-time antara klien dan server.
Penggunaan Dasar
Terapkan middleware WebSocket memakai middleware bawaan Deserve:
import { Mware, Router } from '@neabyte/deserve'
const router = new Router()
// Upgrade /ws dan sapa saat connect
router.use(
Mware.websocket({
listener: '/ws',
onConnect: (socket, event, ctx) => {
console.log('WebSocket connected:', ctx.get.url())
socket.send('Welcome')
}
})
)
await router.serve(8000)Opsi WebSocket
listener
Tentukan prefix path untuk upgrade WebSocket:
listener: '/ws' // Cocok /ws, /ws/chat, /ws/room/123
listener: '/api/ws' // Cocok /api/ws, /api/ws/dataPencocokan sadar-batas, jadi path harus sama persis dengan listener atau berlanjut dengan /. Dengan listener: '/ws', request ke /ws atau /ws/chat di-upgrade, tapi /wsfoo tidak. Garis miring di akhir listener dipangkas, jadi /ws/ berperilaku sama dengan /ws. Mengatur listener: '/' mencocokkan setiap path.
Penting: Middleware hanya mengupgrade request yang:
- Membawa header
Upgrade: websocket - Memakai metode
GET - Cocok dengan path
listenerseperti dijelaskan di atas
Tanpa listener, middleware meneruskan setiap request dan tidak pernah mengupgrade.
allowedOrigins
Kontrol origin handshake mana yang diterima, yang mencegah pembajakan WebSocket lintas situs:
allowedOrigins: '*' // Terima origin apa pun
allowedOrigins: ['https://example.com', 'https://app.example.com'] // AllowlistSaat allowedOrigins dibiarkan undefined, hanya handshake same-origin yang diterima, dan handshake tanpa header Origin diloloskan karena tidak ada kebijakan yang disetel. Begitu allowlist atau '*' dikonfigurasi, Origin yang hilang langsung ditolak dan upgrade dibatalkan, yang menutup celah di mana header yang sekadar dihilangkan bisa lolos dari kebijakan. Origin yang ditolak mengembalikan 403 Forbidden.
onConnect
Menangani koneksi WebSocket baru:
onConnect: (socket: WebSocket, event: Event, ctx: Context) => {
console.log('Client connected:', ctx.get.url())
socket.send(JSON.stringify({
type: 'welcome',
message: 'Connected'
}))
}onMessage
Menangani pesan WebSocket masuk:
onMessage: (socket: WebSocket, event: MessageEvent, ctx: Context) => {
console.log('Received:', event.data)
try {
const data = JSON.parse(event.data as string)
socket.send(JSON.stringify({
echo: data
}))
} catch {
socket.send(JSON.stringify({
error: 'Invalid JSON'
}))
}
}onDisconnect
Menangani pemutusan koneksi WebSocket. CloseEvent menyediakan code, reason, dan wasClean:
onDisconnect: (socket: WebSocket, event: CloseEvent, ctx: Context) => {
console.log('Client disconnected:', event.code, event.reason, event.wasClean)
}onError
Menangani error WebSocket:
onError: (socket: WebSocket, event: Event, ctx: Context) => {
console.error('WebSocket error:', event, 'on', ctx.get.url())
}Contoh Lengkap
import { Mware, Router } from '@neabyte/deserve'
const router = new Router({
routes: { directory: './routes' }
})
router.use(
Mware.websocket({
listener: '/ws',
onConnect: (socket, event, ctx) => {
console.log(`WebSocket connected: ${ctx.get.url()}`)
socket.send(
JSON.stringify({
type: 'welcome',
message: 'Connected to Deserve WebSocket server'
})
)
},
onMessage: (socket, event, ctx) => {
console.log(`Message from ${ctx.get.url()}:`, event.data)
try {
const data = JSON.parse(event.data as string)
if (data.type === 'ping') {
socket.send(
JSON.stringify({
type: 'pong',
timestamp: Date.now()
})
)
} else {
socket.send(
JSON.stringify({
type: 'echo',
original: data,
timestamp: Date.now()
})
)
}
} catch {
socket.send(
JSON.stringify({
type: 'error',
message: 'Invalid JSON'
})
)
}
},
onDisconnect: (socket, event, ctx) => {
console.log(`WebSocket disconnected: ${ctx.get.url()} code=${event.code} reason=${event.reason}`)
},
onError: (socket, event, ctx) => {
console.error(`WebSocket error on ${ctx.get.url()}:`, event)
}
})
)
await router.serve(8000)Penggunaan Sisi Klien
Hubungkan dari browser dengan WebSocket API native:
const socket = new WebSocket('ws://localhost:8000/ws')
socket.addEventListener('open', () => {
console.log('Connected')
socket.send(
JSON.stringify({
type: 'message',
text: 'Hello'
})
)
})
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
console.log('Received:', data)
})
socket.addEventListener('close', () => {
console.log('Disconnected')
})
socket.addEventListener('error', (error) => {
console.error('Error:', error)
})Properti WebSocket
Akses properti WebSocket di dalam handler:
onConnect: (socket, event, ctx) => {
console.log('URL:', socket.url)
console.log('Protocol:', socket.protocol)
console.log('State:', socket.readyState)
// 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
}Penanganan Error
Handshake yang ditolak diarahkan lewat error handler alih-alih melempar saat setup:
- Origin tidak diizinkan gagal dengan 403 dan pesan
WebSocket handshake rejected because the Origin is not allowed - Versi hilang gagal dengan 400 dan pesan
WebSocket handshake requires Sec-WebSocket-Version 13 - Versi salah mengembalikan 426 Upgrade Required dengan header
Sec-WebSocket-Version: 13danUpgrade: websocket - Upgrade malformed gagal dengan 400 dan pesan
WebSocket handshake is malformed because ...
Tiap penolakan juga memancarkan event websocket:rejected dengan alasannya, dibahas di Referensi Event. Semua kegagalan dialirkan ke error handler terpusat, jadi bentuk response di sana atau andalkan perilaku default.
Integrasi Dengan CORS
WebSocket berpasangan dengan klien ber-CORS seperti ini:
import { Mware, Router } from '@neabyte/deserve'
const router = new Router()
// CORS menangani HTTP, WebSocket menangani upgrade
router.use(Mware.cors({
origin: '*'
}))
router.use(Mware.websocket({
listener: '/ws'
}))
await router.serve(8000)Middleware CORS menangani HTTP request, sementara middleware WebSocket menangani upgrade, dan keduanya berjalan bersama tanpa konflik.