feat(node-ws): Reject unexpected WebSocket connections (#973)
* Added rejection of WebSocket connections when the app does not expect them * added changeset * Updated waiter names; removed strict optionpull/975/head
parent
57b9f5dbdc
commit
6f90a574c4
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/node-ws': minor
|
||||
---
|
||||
|
||||
Added rejection of WebSocket connections when the app does not expect them
|
|
@ -51,6 +51,40 @@ describe('WebSocket helper', () => {
|
|||
expect(await mainPromise).toBe(true)
|
||||
})
|
||||
|
||||
it('Should be rejected if upgradeWebSocket is not used', async () => {
|
||||
app.get(
|
||||
'/', (c)=>c.body('')
|
||||
)
|
||||
|
||||
{
|
||||
const ws = new WebSocket('ws://localhost:3030/')
|
||||
const mainPromise = new Promise<boolean>((resolve) => {
|
||||
ws.onerror = () => {
|
||||
resolve(true)
|
||||
}
|
||||
ws.onopen = () => {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
|
||||
expect(await mainPromise).toBe(true)
|
||||
}
|
||||
|
||||
{ //also should rejected on fallback
|
||||
const ws = new WebSocket('ws://localhost:3030/notFound')
|
||||
const mainPromise = new Promise<boolean>((resolve) => {
|
||||
ws.onerror = () => {
|
||||
resolve(true)
|
||||
}
|
||||
ws.onopen = () => {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
|
||||
expect(await mainPromise).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should be able to connect', async () => {
|
||||
const mainPromise = new Promise<boolean>((resolve) =>
|
||||
app.get(
|
||||
|
|
|
@ -5,6 +5,7 @@ import { WebSocketServer } from 'ws'
|
|||
import type { IncomingMessage } from 'http'
|
||||
import type { Server } from 'node:http'
|
||||
import type { Http2SecureServer, Http2Server } from 'node:http2'
|
||||
import type { Duplex } from 'node:stream'
|
||||
import { CloseEvent } from './events'
|
||||
|
||||
export interface NodeWebSocket {
|
||||
|
@ -24,25 +25,25 @@ export interface NodeWebSocketInit {
|
|||
*/
|
||||
export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
||||
const wss = new WebSocketServer({ noServer: true })
|
||||
const waiter = new Map<IncomingMessage, (ws: WebSocket) => void>()
|
||||
const waiterMap = new Map<IncomingMessage, { resolve: (ws: WebSocket) => void, response: Response }>()
|
||||
|
||||
wss.on('connection', (ws, request) => {
|
||||
const waiterFn = waiter.get(request)
|
||||
if (waiterFn) {
|
||||
waiterFn(ws)
|
||||
waiter.delete(request)
|
||||
const waiter = waiterMap.get(request)
|
||||
if (waiter) {
|
||||
waiter.resolve(ws)
|
||||
waiterMap.delete(request)
|
||||
}
|
||||
})
|
||||
|
||||
const nodeUpgradeWebSocket = (request: IncomingMessage) => {
|
||||
const nodeUpgradeWebSocket = (request: IncomingMessage, response: Response) => {
|
||||
return new Promise<WebSocket>((resolve) => {
|
||||
waiter.set(request, resolve)
|
||||
waiterMap.set(request, { resolve, response })
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
injectWebSocket(server) {
|
||||
server.on('upgrade', async (request, socket, head) => {
|
||||
server.on('upgrade', async (request, socket: Duplex, head) => {
|
||||
const url = new URL(request.url ?? '/', init.baseUrl ?? 'http://localhost')
|
||||
const headers = new Headers()
|
||||
for (const key in request.headers) {
|
||||
|
@ -52,11 +53,25 @@ export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
|||
}
|
||||
headers.append(key, Array.isArray(value) ? value[0] : value)
|
||||
}
|
||||
await init.app.request(
|
||||
|
||||
const response = await init.app.request(
|
||||
url,
|
||||
{ headers: headers },
|
||||
{ incoming: request, outgoing: undefined }
|
||||
)
|
||||
|
||||
const waiter = waiterMap.get(request)
|
||||
if (!waiter || waiter.response !== response) {
|
||||
socket.end(
|
||||
'HTTP/1.1 400 Bad Request\r\n' +
|
||||
'Connection: close\r\n' +
|
||||
'Content-Length: 0\r\n' +
|
||||
'\r\n'
|
||||
)
|
||||
waiterMap.delete(request)
|
||||
return
|
||||
}
|
||||
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
wss.emit('connection', ws, request)
|
||||
})
|
||||
|
@ -70,8 +85,9 @@ export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
|||
return
|
||||
}
|
||||
|
||||
const response = new Response()
|
||||
;(async () => {
|
||||
const ws = await nodeUpgradeWebSocket(c.env.incoming)
|
||||
const ws = await nodeUpgradeWebSocket(c.env.incoming, response)
|
||||
const events = await createEvents(c)
|
||||
|
||||
const ctx: WSContext<WebSocket> = {
|
||||
|
@ -116,7 +132,7 @@ export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
|
|||
})
|
||||
})()
|
||||
|
||||
return new Response()
|
||||
return response
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue