Skip to content

Commit 0abea11

Browse files
authored
websocket server only needs to reply with a single subprotocol (#2845)
* websocket server only needs to reply with a single subprotocol * fixup * fixup * fixup
1 parent c49058b commit 0abea11

File tree

4 files changed

+89
-5
lines changed

4 files changed

+89
-5
lines changed

lib/web/fetch/util.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1476,5 +1476,6 @@ module.exports = {
14761476
buildContentRange,
14771477
parseMetadata,
14781478
createInflate,
1479-
extractMimeType
1479+
extractMimeType,
1480+
getDecodeSplit
14801481
}

lib/web/websocket/connection.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const { CloseEvent } = require('./events')
1313
const { makeRequest } = require('../fetch/request')
1414
const { fetching } = require('../fetch/index')
1515
const { Headers } = require('../fetch/headers')
16+
const { getDecodeSplit } = require('../fetch/util')
1617
const { kHeadersList } = require('../../core/symbols')
1718

1819
/** @type {import('crypto')} */
@@ -176,9 +177,18 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish, options)
176177
// the WebSocket Connection_.
177178
const secProtocol = response.headersList.get('Sec-WebSocket-Protocol')
178179

179-
if (secProtocol !== null && secProtocol !== request.headersList.get('Sec-WebSocket-Protocol')) {
180-
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
181-
return
180+
if (secProtocol !== null) {
181+
const requestProtocols = getDecodeSplit('sec-websocket-protocol', request.headersList)
182+
183+
// The client can request that the server use a specific subprotocol by
184+
// including the |Sec-WebSocket-Protocol| field in its handshake. If it
185+
// is specified, the server needs to include the same field and one of
186+
// the selected subprotocol values in its response for the connection to
187+
// be established.
188+
if (!requestProtocols.includes(secProtocol)) {
189+
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
190+
return
191+
}
182192
}
183193

184194
response.socket.on('data', onSocketData)

test/websocket/issue-2844.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict'
2+
3+
const { test } = require('node:test')
4+
const { once } = require('node:events')
5+
const { WebSocketServer } = require('ws')
6+
const { WebSocket } = require('../..')
7+
const { tspl } = require('@matteo.collina/tspl')
8+
9+
test('The server must reply with at least one subprotocol the client sends', async (t) => {
10+
const { completed, deepStrictEqual, fail } = tspl(t, { plan: 2 })
11+
12+
const wss = new WebSocketServer({
13+
handleProtocols: (protocols) => {
14+
deepStrictEqual(protocols, new Set(['msgpack', 'json']))
15+
16+
return protocols.values().next().value
17+
},
18+
port: 0
19+
})
20+
21+
wss.on('connection', (ws) => {
22+
ws.on('error', fail)
23+
ws.send('something')
24+
})
25+
26+
await once(wss, 'listening')
27+
28+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
29+
protocols: ['msgpack', 'json']
30+
})
31+
32+
ws.onerror = fail
33+
ws.onopen = () => deepStrictEqual(ws.protocol, 'msgpack')
34+
35+
t.after(() => {
36+
wss.close()
37+
ws.close()
38+
})
39+
40+
await completed
41+
})
42+
43+
test('The connection fails when the client sends subprotocols that the server does not responc with', async (t) => {
44+
const { completed, fail, ok } = tspl(t, { plan: 1 })
45+
46+
const wss = new WebSocketServer({
47+
handleProtocols: () => false,
48+
port: 0
49+
})
50+
51+
wss.on('connection', (ws) => {
52+
ws.on('error', fail)
53+
ws.send('something')
54+
})
55+
56+
await once(wss, 'listening')
57+
58+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
59+
protocols: ['json']
60+
})
61+
62+
ws.onerror = ok.bind(null, true)
63+
// The server will try to send 'something', this ensures that the connection
64+
// fails during the handshake and doesn't receive any messages.
65+
ws.onmessage = fail
66+
67+
t.after(() => {
68+
wss.close()
69+
ws.close()
70+
})
71+
72+
await completed
73+
})

test/wpt/server/websocket.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { server } from './server.mjs'
88

99
const wss = new WebSocketServer({
1010
server,
11-
handleProtocols: (protocols) => [...protocols].join(', ')
11+
handleProtocols: (protocols) => protocols.values().next().value
1212
})
1313

1414
wss.on('connection', (ws, request) => {

0 commit comments

Comments
 (0)