Lewati ke konten

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:

typescript
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:

typescript
listener: '/ws' // Cocok /ws, /ws/chat, /ws/room/123
listener: '/api/ws' // Cocok /api/ws, /api/ws/data

Pencocokan 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 listener seperti 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:

typescript
allowedOrigins: '*' // Terima origin apa pun
allowedOrigins: ['https://example.com', 'https://app.example.com'] // Allowlist

Saat 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:

typescript
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:

typescript
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:

typescript
onDisconnect: (socket: WebSocket, event: CloseEvent, ctx: Context) => {
  console.log('Client disconnected:', event.code, event.reason, event.wasClean)
}

onError

Menangani error WebSocket:

typescript
onError: (socket: WebSocket, event: Event, ctx: Context) => {
  console.error('WebSocket error:', event, 'on', ctx.get.url())
}

Contoh Lengkap

typescript
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:

typescript
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:

typescript
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: 13 dan Upgrade: 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:

typescript
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.

Dirilis di bawah Lisensi MIT.